mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2025-01-11 09:00:06 +00:00
spirv-fuzz: Relax type checking for int contants (#3573)
Right now, TransformationRecordSynonymousConstants requires the type ids of two candidate constants to be exactly the same. This PR adds an exception for integer constants, which can be considered equivalent even if their signedness is different. This applies to both integers and vector constants. The IsApplicable method of ReplaceIdWithSynonym is also updated so that, in the case of two integer constants which don't have the same type, they can only be swapped in particular instructions (those that don't take the signedness into consideration). Fixes #3536.
This commit is contained in:
parent
f8920bcfab
commit
767518e8e1
@ -1241,6 +1241,38 @@ void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
|
||||
UpdateModuleIdBound(ir_context, result_id);
|
||||
}
|
||||
|
||||
bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
|
||||
uint32_t type2_id) {
|
||||
if (type1_id == type2_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto type1 = ir_context->get_type_mgr()->GetType(type1_id);
|
||||
auto type2 = ir_context->get_type_mgr()->GetType(type2_id);
|
||||
|
||||
// Integer scalar types must have the same width
|
||||
if (type1->AsInteger() && type2->AsInteger()) {
|
||||
return type1->AsInteger()->width() == type2->AsInteger()->width();
|
||||
}
|
||||
|
||||
// Integer vector types must have the same number of components and their
|
||||
// component types must be integers with the same width.
|
||||
if (type1->AsVector() && type2->AsVector()) {
|
||||
auto component_type1 = type1->AsVector()->element_type()->AsInteger();
|
||||
auto component_type2 = type2->AsVector()->element_type()->AsInteger();
|
||||
|
||||
// Only check the component count and width if they are integer.
|
||||
if (component_type1 && component_type2) {
|
||||
return type1->AsVector()->element_count() ==
|
||||
type2->AsVector()->element_count() &&
|
||||
component_type1->width() == component_type2->width();
|
||||
}
|
||||
}
|
||||
|
||||
// In all other cases, the types cannot be considered equal.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
@ -456,6 +456,13 @@ inline uint32_t FloatToWord(float value) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if any of the following is true:
|
||||
// - |type1_id| and |type2_id| are the same id
|
||||
// - |type1_id| and |type2_id| refer to integer scalar or vector types, only
|
||||
// differing by their signedness.
|
||||
bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
|
||||
uint32_t type2_id);
|
||||
|
||||
} // namespace fuzzerutil
|
||||
|
||||
} // namespace fuzz
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
#include "transformation_record_synonymous_constants.h"
|
||||
|
||||
#include "source/fuzz/fuzzer_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace fuzz {
|
||||
|
||||
@ -76,19 +78,17 @@ bool TransformationRecordSynonymousConstants::AreEquivalentConstants(
|
||||
return false;
|
||||
}
|
||||
|
||||
// The type ids must be the same
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536): Somehow
|
||||
// relax this for integers (so that unsigned integer and signed integer are
|
||||
// considered the same type)
|
||||
if (def_1->type_id() != def_2->type_id()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto constant1 = ir_context->get_constant_mgr()->GetConstantFromInst(def_1);
|
||||
auto constant2 = ir_context->get_constant_mgr()->GetConstantFromInst(def_2);
|
||||
|
||||
assert(constant1 && constant2 && "The ids must refer to constants.");
|
||||
|
||||
// The types must be compatible.
|
||||
if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, def_1->type_id(),
|
||||
def_2->type_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If either constant is null, the other is equivalent iff it is zero-like
|
||||
if (constant1->AsNullConstant()) {
|
||||
return constant2->IsZero();
|
||||
|
@ -32,16 +32,17 @@ class TransformationRecordSynonymousConstants : public Transformation {
|
||||
// - |message_.constant_id| and |message_.synonym_id| are distinct ids
|
||||
// of constants
|
||||
// - |message_.constant_id| and |message_.synonym_id| refer to constants
|
||||
// that are equal or equivalent.
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536): Signed and
|
||||
// unsigned integers are currently considered non-equivalent
|
||||
// Two integers with the same width and value are equal, even if one is
|
||||
// signed and the other is not.
|
||||
// Constants are equivalent:
|
||||
// - if both of them represent zero-like values of the same type
|
||||
// - if they are composite constants with the same type and their
|
||||
// components are pairwise equivalent.
|
||||
// - |constant1_id| and |constant2_id| may not be irrelevant.
|
||||
// that are equivalent.
|
||||
// Constants are equivalent if at least one of the following holds:
|
||||
// - they are equal (i.e. they have the same type ids and equal values)
|
||||
// - both of them represent zero-like values of compatible types
|
||||
// - they are composite constants with compatible types and their
|
||||
// components are pairwise equivalent
|
||||
// Two types are compatible if at least one of the following holds:
|
||||
// - they have the same id
|
||||
// - they are integer scalar types with the same width
|
||||
// - they are integer vectors and their components have the same width
|
||||
// (this is always the case if the components are equivalent)
|
||||
bool IsApplicable(
|
||||
opt::IRContext* ir_context,
|
||||
const TransformationContext& transformation_context) const override;
|
||||
|
@ -57,6 +57,24 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t type_id_of_interest =
|
||||
ir_context->get_def_use_mgr()->GetDef(id_of_interest)->type_id();
|
||||
uint32_t type_id_synonym = ir_context->get_def_use_mgr()
|
||||
->GetDef(message_.synonymous_id())
|
||||
->type_id();
|
||||
|
||||
// If the id of interest and the synonym are scalar or vector integer
|
||||
// constants with different signedness, their use can only be swapped if the
|
||||
// instruction is agnostic to the signedness of the operand.
|
||||
if (type_id_of_interest != type_id_synonym &&
|
||||
fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_of_interest,
|
||||
type_id_synonym) &&
|
||||
!IsAgnosticToSignednessOfOperand(
|
||||
use_instruction->opcode(),
|
||||
message_.id_use_descriptor().in_operand_index())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the use suitable for being replaced in principle?
|
||||
if (!UseCanBeReplacedWithSynonym(
|
||||
ir_context, use_instruction,
|
||||
@ -182,5 +200,46 @@ bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all
|
||||
// opcodes that are agnostic to signedness of operands to function.
|
||||
// This is not exhaustive yet.
|
||||
bool TransformationReplaceIdWithSynonym::IsAgnosticToSignednessOfOperand(
|
||||
SpvOp opcode, uint32_t use_in_operand_index) {
|
||||
switch (opcode) {
|
||||
case SpvOpSNegate:
|
||||
case SpvOpNot:
|
||||
case SpvOpIAdd:
|
||||
case SpvOpISub:
|
||||
case SpvOpIMul:
|
||||
case SpvOpSDiv:
|
||||
case SpvOpSRem:
|
||||
case SpvOpSMod:
|
||||
case SpvOpShiftRightLogical:
|
||||
case SpvOpShiftRightArithmetic:
|
||||
case SpvOpShiftLeftLogical:
|
||||
case SpvOpBitwiseOr:
|
||||
case SpvOpBitwiseXor:
|
||||
case SpvOpBitwiseAnd:
|
||||
case SpvOpIEqual:
|
||||
case SpvOpINotEqual:
|
||||
case SpvOpULessThan:
|
||||
case SpvOpSLessThan:
|
||||
case SpvOpUGreaterThan:
|
||||
case SpvOpSGreaterThan:
|
||||
case SpvOpULessThanEqual:
|
||||
case SpvOpSLessThanEqual:
|
||||
case SpvOpUGreaterThanEqual:
|
||||
case SpvOpSGreaterThanEqual:
|
||||
return true;
|
||||
case SpvOpAccessChain:
|
||||
// The signedness of indices does not matter.
|
||||
return use_in_operand_index > 0;
|
||||
default:
|
||||
// Conservatively assume that the id cannot be swapped in other
|
||||
// instructions.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
@ -64,6 +64,13 @@ class TransformationReplaceIdWithSynonym : public Transformation {
|
||||
opt::Instruction* use_instruction,
|
||||
uint32_t use_in_operand_index);
|
||||
|
||||
// Returns true if the instruction with opcode |opcode| does not change its
|
||||
// behaviour depending on the signedness of the operand at
|
||||
// |use_in_operand_index|.
|
||||
// Assumes that the operand must be the id of an integer scalar or vector.
|
||||
static bool IsAgnosticToSignednessOfOperand(SpvOp opcode,
|
||||
uint32_t use_in_operand_index);
|
||||
|
||||
private:
|
||||
protobufs::TransformationReplaceIdWithSynonym message_;
|
||||
};
|
||||
|
@ -90,13 +90,19 @@ TEST(TransformationRecordSynonymousConstantsTest, IntConstants) {
|
||||
validator_options);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
#ifndef NDEBUG
|
||||
// %3 is not a constant declaration
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(3, 9).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
ASSERT_DEATH(TransformationRecordSynonymousConstants(3, 9).IsApplicable(
|
||||
context.get(), transformation_context),
|
||||
"The ids must refer to constants.");
|
||||
#endif
|
||||
|
||||
// Swapping the ids gives the same result
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 3).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
#ifndef NDEBUG
|
||||
// %3 is not a constant declaration
|
||||
ASSERT_DEATH(TransformationRecordSynonymousConstants(9, 3).IsApplicable(
|
||||
context.get(), transformation_context),
|
||||
"The ids must refer to constants.");
|
||||
#endif
|
||||
|
||||
// The two constants must be different
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 9).IsApplicable(
|
||||
@ -129,14 +135,12 @@ TEST(TransformationRecordSynonymousConstantsTest, IntConstants) {
|
||||
ApplyTransformationAndCheckFactManager(13, 22, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536):
|
||||
// Relax type check for integers. Uncomment this code once the issue is fixed.
|
||||
// // %13 and %20 are equal even if %13 is signed and %20 is unsigned
|
||||
// ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 20).IsApplicable(
|
||||
// context.get(), transformation_context));
|
||||
//
|
||||
// ApplyTransformationAndCheckFactManager(13, 20, context.get(),
|
||||
// &transformation_context);
|
||||
// %13 and %20 are equal even if %13 is signed and %20 is unsigned
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 20).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(13, 20, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// %9 and %11 are equivalent (OpConstant with value 0 and OpConstantNull)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(9, 11).IsApplicable(
|
||||
@ -680,6 +684,104 @@ TEST(TransformationRecordSynonymousConstantsTest, ArrayCompositeConstants) {
|
||||
context.get(), transformation_context));
|
||||
}
|
||||
|
||||
TEST(TransformationRecordSynonymousConstantsTest, IntVectors) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main" %3
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpDecorate %3 Location 0
|
||||
%4 = OpTypeVoid
|
||||
%5 = OpTypeFunction %4
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypeInt 32 0
|
||||
%8 = OpTypeVector %6 4
|
||||
%9 = OpTypeVector %7 4
|
||||
%10 = OpTypePointer Function %8
|
||||
%11 = OpTypePointer Function %8
|
||||
%12 = OpConstant %6 0
|
||||
%13 = OpConstant %7 0
|
||||
%14 = OpConstant %6 1
|
||||
%25 = OpConstant %7 1
|
||||
%15 = OpConstantComposite %8 %12 %12 %12 %12
|
||||
%16 = OpConstantComposite %9 %13 %13 %13 %13
|
||||
%17 = OpConstantComposite %8 %14 %12 %12 %14
|
||||
%18 = OpConstantComposite %9 %25 %13 %13 %25
|
||||
%19 = OpConstantNull %8
|
||||
%20 = OpConstantNull %9
|
||||
%21 = OpTypeFloat 32
|
||||
%22 = OpTypeVector %21 4
|
||||
%23 = OpTypePointer Output %22
|
||||
%3 = OpVariable %23 Output
|
||||
%2 = OpFunction %4 None %5
|
||||
%24 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_4;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
// %15 and %17 are not equivalent (having non-equivalent components)
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(15, 17).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
// %17 and %19 are not equivalent (%19 is null, %17 is non-zero)
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(17, 19).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
// %17 and %20 are not equivalent (%19 is null, %20 is non-zero)
|
||||
ASSERT_FALSE(TransformationRecordSynonymousConstants(17, 20).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
// %15 and %16 are equivalent (having pairwise equivalent components)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 16).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(15, 16, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// %17 and %18 are equivalent (having pairwise equivalent components)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(17, 18).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(17, 18, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// %19 and %20 are equivalent (both null vectors with compatible types)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(19, 20).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(19, 20, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// %15 and %19 are equivalent (they have compatible types, %15 is zero-like
|
||||
// and %19 is null)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 19).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(15, 19, context.get(),
|
||||
&transformation_context);
|
||||
|
||||
// %15 and %20 are equivalent (they have compatible types, %15 is zero-like
|
||||
// and %20 is null)
|
||||
ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 20).IsApplicable(
|
||||
context.get(), transformation_context));
|
||||
|
||||
ApplyTransformationAndCheckFactManager(15, 20, context.get(),
|
||||
&transformation_context);
|
||||
}
|
||||
|
||||
TEST(TransformationRecordSynonymousConstantsTest, FirstIrrelevantConstant) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
|
@ -1450,6 +1450,271 @@ TEST(TransformationReplaceIdWithSynonymTest,
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
}
|
||||
|
||||
TEST(TransformationReplaceIdWithSynonymTest, EquivalentIntegerConstants) {
|
||||
// This checks that replacing an integer constant with an equivalent one with
|
||||
// different signedness is allowed only when valid.
|
||||
const std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
OpName %3 "a"
|
||||
OpDecorate %3 RelaxedPrecision
|
||||
%4 = OpTypeVoid
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeFunction %4
|
||||
%8 = OpTypeInt 32 1
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpConstant %8 1
|
||||
%11 = OpTypeInt 32 0
|
||||
%12 = OpTypePointer Function %11
|
||||
%13 = OpConstant %11 1
|
||||
%2 = OpFunction %4 None %7
|
||||
%14 = OpLabel
|
||||
%3 = OpVariable %9 Function
|
||||
%15 = OpSNegate %8 %10
|
||||
%16 = OpIAdd %8 %10 %10
|
||||
%17 = OpSDiv %8 %10 %10
|
||||
%18 = OpUDiv %11 %13 %13
|
||||
%19 = OpBitwiseAnd %8 %10 %10
|
||||
%20 = OpSelect %8 %6 %10 %17
|
||||
%21 = OpIEqual %5 %10 %10
|
||||
OpStore %3 %10
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
// Add synonym fact relating %10 and %13 (equivalent integer constant with
|
||||
// different signedness).
|
||||
transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 13),
|
||||
context.get());
|
||||
|
||||
// Legal because OpSNegate always considers the integer as signed
|
||||
auto replacement1 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(10, MakeInstructionDescriptor(15, SpvOpSNegate, 0),
|
||||
0),
|
||||
13);
|
||||
ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
|
||||
replacement1.Apply(context.get(), &transformation_context);
|
||||
|
||||
// Legal because OpIAdd does not care about the signedness of the operands
|
||||
auto replacement2 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(10, MakeInstructionDescriptor(16, SpvOpIAdd, 0), 0),
|
||||
13);
|
||||
ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
|
||||
replacement2.Apply(context.get(), &transformation_context);
|
||||
|
||||
// Legal because OpSDiv does not care about the signedness of the operands
|
||||
auto replacement3 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(10, MakeInstructionDescriptor(17, SpvOpSDiv, 0), 0),
|
||||
13);
|
||||
ASSERT_TRUE(replacement3.IsApplicable(context.get(), transformation_context));
|
||||
replacement3.Apply(context.get(), &transformation_context);
|
||||
|
||||
// Not legal because OpUDiv requires unsigned integers
|
||||
ASSERT_FALSE(TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(
|
||||
13, MakeInstructionDescriptor(18, SpvOpUDiv, 0), 0),
|
||||
10)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Legal because OpSDiv does not care about the signedness of the operands
|
||||
auto replacement4 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(10, MakeInstructionDescriptor(19, SpvOpBitwiseAnd, 0),
|
||||
0),
|
||||
13);
|
||||
ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
|
||||
replacement4.Apply(context.get(), &transformation_context);
|
||||
|
||||
// Not legal because OpSelect requires both operands to have the same type as
|
||||
// the result type
|
||||
ASSERT_FALSE(TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(
|
||||
10, MakeInstructionDescriptor(20, SpvOpUDiv, 0), 1),
|
||||
13)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Not legal because OpStore requires the object to match the type pointed
|
||||
// to by the pointer.
|
||||
ASSERT_FALSE(TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(
|
||||
10, MakeInstructionDescriptor(21, SpvOpStore, 0), 1),
|
||||
13)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
OpName %3 "a"
|
||||
OpDecorate %3 RelaxedPrecision
|
||||
%4 = OpTypeVoid
|
||||
%5 = OpTypeBool
|
||||
%6 = OpConstantTrue %5
|
||||
%7 = OpTypeFunction %4
|
||||
%8 = OpTypeInt 32 1
|
||||
%9 = OpTypePointer Function %8
|
||||
%10 = OpConstant %8 1
|
||||
%11 = OpTypeInt 32 0
|
||||
%12 = OpTypePointer Function %11
|
||||
%13 = OpConstant %11 1
|
||||
%2 = OpFunction %4 None %7
|
||||
%14 = OpLabel
|
||||
%3 = OpVariable %9 Function
|
||||
%15 = OpSNegate %8 %13
|
||||
%16 = OpIAdd %8 %13 %10
|
||||
%17 = OpSDiv %8 %13 %10
|
||||
%18 = OpUDiv %11 %13 %13
|
||||
%19 = OpBitwiseAnd %8 %13 %10
|
||||
%20 = OpSelect %8 %6 %10 %17
|
||||
%21 = OpIEqual %5 %10 %10
|
||||
OpStore %3 %10
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
TEST(TransformationReplaceIdWithSynonymTest, EquivalentIntegerVectorConstants) {
|
||||
// This checks that replacing an integer constant with an equivalent one with
|
||||
// different signedness is allowed only when valid.
|
||||
const std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
OpName %3 "a"
|
||||
OpDecorate %3 RelaxedPrecision
|
||||
OpDecorate %4 RelaxedPrecision
|
||||
%5 = OpTypeVoid
|
||||
%6 = OpTypeFunction %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%9 = OpTypeVector %7 4
|
||||
%10 = OpTypeVector %8 4
|
||||
%11 = OpTypePointer Function %9
|
||||
%12 = OpConstant %7 1
|
||||
%13 = OpConstant %8 1
|
||||
%14 = OpConstantComposite %9 %12 %12 %12 %12
|
||||
%15 = OpConstantComposite %10 %13 %13 %13 %13
|
||||
%16 = OpTypePointer Function %7
|
||||
%2 = OpFunction %5 None %6
|
||||
%17 = OpLabel
|
||||
%3 = OpVariable %11 Function
|
||||
%18 = OpIAdd %9 %14 %14
|
||||
OpStore %3 %14
|
||||
%19 = OpAccessChain %16 %3 %13
|
||||
%4 = OpLoad %7 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
FactManager fact_manager;
|
||||
spvtools::ValidatorOptions validator_options;
|
||||
TransformationContext transformation_context(&fact_manager,
|
||||
validator_options);
|
||||
|
||||
// Add synonym fact relating %10 and %13 (equivalent integer vectors with
|
||||
// different signedness).
|
||||
transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 15),
|
||||
context.get());
|
||||
|
||||
// Legal because OpIAdd does not consider the signedness of the operands
|
||||
auto replacement1 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(14, MakeInstructionDescriptor(18, SpvOpIAdd, 0), 0),
|
||||
15);
|
||||
ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
|
||||
replacement1.Apply(context.get(), &transformation_context);
|
||||
|
||||
// Not legal because OpStore requires the object to match the type pointed
|
||||
// to by the pointer.
|
||||
ASSERT_FALSE(TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(
|
||||
14, MakeInstructionDescriptor(18, SpvOpStore, 0), 1),
|
||||
15)
|
||||
.IsApplicable(context.get(), transformation_context));
|
||||
|
||||
// Add synonym fact relating %12 and %13 (equivalent integer constants with
|
||||
// different signedness).
|
||||
transformation_context.GetFactManager()->AddFact(MakeSynonymFact(12, 13),
|
||||
context.get());
|
||||
|
||||
// Legal because the indices of OpAccessChain are always treated as signed
|
||||
auto replacement2 = TransformationReplaceIdWithSynonym(
|
||||
MakeIdUseDescriptor(
|
||||
13, MakeInstructionDescriptor(19, SpvOpAccessChain, 0), 1),
|
||||
12);
|
||||
ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
|
||||
replacement2.Apply(context.get(), &transformation_context);
|
||||
|
||||
ASSERT_TRUE(IsValid(env, context.get()));
|
||||
|
||||
const std::string after_transformation = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %2 "main"
|
||||
OpExecutionMode %2 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpName %2 "main"
|
||||
OpName %3 "a"
|
||||
OpDecorate %3 RelaxedPrecision
|
||||
OpDecorate %4 RelaxedPrecision
|
||||
%5 = OpTypeVoid
|
||||
%6 = OpTypeFunction %5
|
||||
%7 = OpTypeInt 32 1
|
||||
%8 = OpTypeInt 32 0
|
||||
%9 = OpTypeVector %7 4
|
||||
%10 = OpTypeVector %8 4
|
||||
%11 = OpTypePointer Function %9
|
||||
%12 = OpConstant %7 1
|
||||
%13 = OpConstant %8 1
|
||||
%14 = OpConstantComposite %9 %12 %12 %12 %12
|
||||
%15 = OpConstantComposite %10 %13 %13 %13 %13
|
||||
%16 = OpTypePointer Function %7
|
||||
%2 = OpFunction %5 None %6
|
||||
%17 = OpLabel
|
||||
%3 = OpVariable %11 Function
|
||||
%18 = OpIAdd %9 %15 %14
|
||||
OpStore %3 %14
|
||||
%19 = OpAccessChain %16 %3 %12
|
||||
%4 = OpLoad %7 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace fuzz
|
||||
} // namespace spvtools
|
||||
|
Loading…
Reference in New Issue
Block a user