From 5f8cdd8b4529aa925cb4cccccb9516fd4216de74 Mon Sep 17 00:00:00 2001 From: Stefano Milizia Date: Fri, 10 Jul 2020 13:02:14 +0000 Subject: [PATCH] Implement transformation to record synonymous constants. (#3494) Adds a fact-only transformation that records that two constants in the module are synonymous. --- source/fuzz/CMakeLists.txt | 2 + source/fuzz/protobufs/spvtoolsfuzz.proto | 21 ++ source/fuzz/transformation.cpp | 5 + ...sformation_record_synonymous_constants.cpp | 107 ++++++ ...ansformation_record_synonymous_constants.h | 59 ++++ test/fuzz/CMakeLists.txt | 1 + ...ation_record_synonymous_constants_test.cpp | 325 ++++++++++++++++++ utils/check_copyright.py | 3 +- 8 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 source/fuzz/transformation_record_synonymous_constants.cpp create mode 100644 source/fuzz/transformation_record_synonymous_constants.h create mode 100644 test/fuzz/transformation_record_synonymous_constants_test.cpp diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 019d2e7f..9adb4009 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -128,6 +128,7 @@ if(SPIRV_BUILD_FUZZER) transformation_permute_function_parameters.h transformation_permute_phi_operands.h transformation_push_id_through_variable.h + transformation_record_synonymous_constants.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h transformation_replace_id_with_synonym.h @@ -243,6 +244,7 @@ if(SPIRV_BUILD_FUZZER) transformation_permute_function_parameters.cpp transformation_permute_phi_operands.cpp transformation_push_id_through_variable.cpp + transformation_record_synonymous_constants.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp transformation_replace_id_with_synonym.cpp diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index dad5260c..eebfccf2 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -385,6 +385,7 @@ message Transformation { TransformationInvertComparisonOperator invert_comparison_operator = 54; TransformationAddImageSampleUnusedComponents add_image_sample_unused_components = 55; TransformationReplaceParameterWithGlobal replace_parameter_with_global = 56; + TransformationRecordSynonymousConstants record_synonymous_constants = 57; // Add additional option using the next available number. } } @@ -1123,6 +1124,26 @@ message TransformationPushIdThroughVariable { } +message TransformationRecordSynonymousConstants { + + // A transformation that, given the IDs to two synonymous constants, + // records the fact that they are synonymous. The module is not changed. + // Two constants are synonymous if: + // - they have the same type (ignoring the presence of integer sign) + // - they have the same opcode (one of OpConstant, OpConstantTrue, + // OpConstantFalse, OpConstantNull) + // - they have the same value + // If the types are the same, OpConstantNull is equivalent to + // OpConstantFalse or OpConstant with value zero. + + // The id of a constant + uint32 constant1_id = 1; + + // The id of the synonym + uint32 constant2_id = 2; + +} + message TransformationReplaceParameterWithGlobal { // Removes parameter with result id |parameter_id| from its function diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index a6b31c43..4adb39c3 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -58,6 +58,7 @@ #include "source/fuzz/transformation_permute_function_parameters.h" #include "source/fuzz/transformation_permute_phi_operands.h" #include "source/fuzz/transformation_push_id_through_variable.h" +#include "source/fuzz/transformation_record_synonymous_constants.h" #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h" #include "source/fuzz/transformation_replace_constant_with_uniform.h" #include "source/fuzz/transformation_replace_id_with_synonym.h" @@ -194,6 +195,10 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kPushIdThroughVariable: return MakeUnique( message.push_id_through_variable()); + case protobufs::Transformation::TransformationCase:: + kRecordSynonymousConstants: + return MakeUnique( + message.record_synonymous_constants()); case protobufs::Transformation::TransformationCase:: kReplaceParameterWithGlobal: return MakeUnique( diff --git a/source/fuzz/transformation_record_synonymous_constants.cpp b/source/fuzz/transformation_record_synonymous_constants.cpp new file mode 100644 index 00000000..422e57e8 --- /dev/null +++ b/source/fuzz/transformation_record_synonymous_constants.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2020 Stefano Milizia +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "transformation_record_synonymous_constants.h" + +namespace spvtools { +namespace fuzz { + +namespace { +bool IsScalarZeroConstant(const opt::analysis::Constant* constant) { + return constant->AsScalarConstant() && constant->IsZero(); +} +} // namespace + +TransformationRecordSynonymousConstants:: + TransformationRecordSynonymousConstants( + const protobufs::TransformationRecordSynonymousConstants& message) + : message_(message) {} + +TransformationRecordSynonymousConstants:: + TransformationRecordSynonymousConstants(uint32_t constant1_id, + uint32_t constant2_id) { + message_.set_constant1_id(constant1_id); + message_.set_constant2_id(constant2_id); +} + +bool TransformationRecordSynonymousConstants::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& /* unused */) const { + // The ids must be different + if (message_.constant1_id() == message_.constant2_id()) { + return false; + } + + auto constant1 = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.constant1_id()); + auto constant2 = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.constant2_id()); + + // The constants must exist + if (constant1 == nullptr || constant2 == nullptr) { + return false; + } + + // If the constants are equal, then they are equivalent + if (constant1 == constant2) { + return true; + } + + // If the constants are two integers (signed or unsigned), they are equal + // if they have the same width and the same data words. + if (constant1->AsIntConstant() && constant2->AsIntConstant() && + constant1->type()->AsInteger()->width() == + constant2->type()->AsInteger()->width() && + constant1->AsIntConstant()->words() == + constant2->AsIntConstant()->words()) { + return true; + } + + // The types must be the same + if (!constant1->type()->IsSame(constant2->type())) { + return false; + } + + // The constants are equivalent if one is null and the other is a static + // constant with value 0. + return (constant1->AsNullConstant() && IsScalarZeroConstant(constant2)) || + (IsScalarZeroConstant(constant1) && constant2->AsNullConstant()); +} + +void TransformationRecordSynonymousConstants::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + protobufs::FactDataSynonym fact_data_synonym; + // Define the two equivalent data descriptors (just containing the ids) + *fact_data_synonym.mutable_data1() = + MakeDataDescriptor(message_.constant1_id(), {}); + *fact_data_synonym.mutable_data2() = + MakeDataDescriptor(message_.constant2_id(), {}); + protobufs::Fact fact; + *fact.mutable_data_synonym_fact() = fact_data_synonym; + + // Add the fact to the fact manager + transformation_context->GetFactManager()->AddFact(fact, ir_context); +} + +protobufs::Transformation TransformationRecordSynonymousConstants::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_record_synonymous_constants() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_record_synonymous_constants.h b/source/fuzz/transformation_record_synonymous_constants.h new file mode 100644 index 00000000..6a1a607d --- /dev/null +++ b/source/fuzz/transformation_record_synonymous_constants.h @@ -0,0 +1,59 @@ +// Copyright (c) 2020 Stefano Milizia +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_RECORD_SYNONYMOUS_CONSTANTS_H +#define SOURCE_FUZZ_TRANSFORMATION_RECORD_SYNONYMOUS_CONSTANTS_H + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationRecordSynonymousConstants : public Transformation { + public: + explicit TransformationRecordSynonymousConstants( + const protobufs::TransformationRecordSynonymousConstants& message); + + TransformationRecordSynonymousConstants(uint32_t constant1_id, + uint32_t constant2_id); + + // - |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. + // 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 scalar + // values of the same type (for example OpConstant of type int and value + // 0 and OpConstantNull of type int). + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds the fact that |message_.constant_id| and |message_.synonym_id| + // are synonyms to the fact manager. The module is not changed. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationRecordSynonymousConstants message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_RECORD_SYNONYMOUS_CONSTANTS diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 0aff9215..fa447370 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -78,6 +78,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_swap_commutable_operands_test.cpp transformation_swap_conditional_branch_operands_test.cpp transformation_toggle_access_chain_instruction_test.cpp + transformation_record_synonymous_constants_test.cpp transformation_vector_shuffle_test.cpp uniform_buffer_element_descriptor_test.cpp) diff --git a/test/fuzz/transformation_record_synonymous_constants_test.cpp b/test/fuzz/transformation_record_synonymous_constants_test.cpp new file mode 100644 index 00000000..a37097da --- /dev/null +++ b/test/fuzz/transformation_record_synonymous_constants_test.cpp @@ -0,0 +1,325 @@ +// Copyright (c) 2020 Stefano Milizia +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_record_synonymous_constants.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +// Apply the TransformationRecordSynonymousConstants defined by the given +// constant1_id and constant2_id and check that the fact that the two +// constants are synonym is recorded. +void ApplyTransformationAndCheckFactManager( + uint32_t constant1_id, uint32_t constant2_id, opt::IRContext* ir_context, + TransformationContext* transformation_context) { + TransformationRecordSynonymousConstants(constant1_id, constant2_id) + .Apply(ir_context, transformation_context); + + ASSERT_TRUE(transformation_context->GetFactManager()->IsSynonymous( + MakeDataDescriptor(constant1_id, {}), + MakeDataDescriptor(constant2_id, {}))); +} + +TEST(TransformationRecordSynonymousConstantsTest, IntConstants) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %17 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %12 "c" + OpName %17 "color" + OpDecorate %8 RelaxedPrecision + OpDecorate %10 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %17 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %19 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 0 + %11 = OpConstantNull %6 + %13 = OpConstant %6 1 + %20 = OpConstant %19 1 + %21 = OpConstant %19 -1 + %22 = OpConstant %6 1 + %14 = OpTypeFloat 32 + %15 = OpTypeVector %14 4 + %16 = OpTypePointer Output %15 + %17 = OpVariable %16 Output + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %12 %13 + 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())); + + // %3 is not a constant declaration + ASSERT_FALSE(TransformationRecordSynonymousConstants(3, 9).IsApplicable( + context.get(), transformation_context)); + + // Swapping the ids gives the same result + ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 3).IsApplicable( + context.get(), transformation_context)); + + // The two constants must be different + ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 9).IsApplicable( + context.get(), transformation_context)); + + // %9 and %13 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 13).IsApplicable( + context.get(), transformation_context)); + + // Swapping the ids gives the same result + ASSERT_FALSE(TransformationRecordSynonymousConstants(13, 9).IsApplicable( + context.get(), transformation_context)); + + // %11 and %13 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(11, 13).IsApplicable( + context.get(), transformation_context)); + + // Swapping the ids gives the same result + ASSERT_FALSE(TransformationRecordSynonymousConstants(13, 11).IsApplicable( + context.get(), transformation_context)); + + // %20 and %21 have different values + ASSERT_FALSE(TransformationRecordSynonymousConstants(20, 21).IsApplicable( + context.get(), transformation_context)); + + // %13 and %22 are equal and thus equivalent (having the same value and type) + ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 22).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(13, 22, 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( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(9, 11, context.get(), + &transformation_context); + + // Swapping the ids gives the same result + ASSERT_TRUE(TransformationRecordSynonymousConstants(11, 9).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(11, 9, context.get(), + &transformation_context); +} + +TEST(TransformationRecordSynonymousConstantsTest, BoolConstants) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %19 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "b" + OpName %19 "color" + OpDecorate %19 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpTypePointer Function %6 + %9 = OpConstantFalse %6 + %20 = OpConstantNull %6 + %11 = OpConstantTrue %6 + %21 = OpConstantFalse %6 + %22 = OpConstantTrue %6 + %16 = OpTypeFloat 32 + %17 = OpTypeVector %16 4 + %18 = OpTypePointer Output %17 + %19 = OpVariable %18 Output + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + %10 = OpLoad %6 %8 + %12 = OpLogicalEqual %6 %10 %11 + OpSelectionMerge %14 None + OpBranchConditional %12 %13 %14 + %13 = OpLabel + OpReturn + %14 = 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())); + + // %9 and %11 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 11).IsApplicable( + context.get(), transformation_context)); + + // %20 and %11 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(20, 11).IsApplicable( + context.get(), transformation_context)); + + // %9 and %21 are equivalent (both OpConstantFalse) + ASSERT_TRUE(TransformationRecordSynonymousConstants(9, 21).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(9, 21, context.get(), + &transformation_context); + + // %11 and %22 are equivalent (both OpConstantTrue) + ASSERT_TRUE(TransformationRecordSynonymousConstants(11, 22).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(11, 22, context.get(), + &transformation_context); + + // %9 and %20 are equivalent (OpConstantFalse and boolean OpConstantNull) + ASSERT_TRUE(TransformationRecordSynonymousConstants(9, 20).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(9, 20, context.get(), + &transformation_context); +} + +TEST(TransformationRecordSynonymousConstantsTest, FloatConstants) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %22 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %12 "c" + OpName %22 "color" + OpDecorate %22 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %11 = OpConstantNull %6 + %13 = OpConstant %6 2 + %26 = OpConstant %6 2 + %16 = OpTypeBool + %20 = OpTypeVector %6 4 + %21 = OpTypePointer Output %20 + %22 = OpVariable %21 Output + %23 = OpConstantComposite %20 %9 %11 %9 %11 + %25 = OpConstantComposite %20 %11 %9 %9 %11 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %12 %13 + %14 = OpLoad %6 %8 + %15 = OpLoad %6 %10 + %17 = OpFOrdEqual %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %24 + %18 = OpLabel + OpStore %22 %23 + OpBranch %19 + %24 = OpLabel + OpStore %22 %25 + OpBranch %19 + %19 = 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())); + + // %9 and %13 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 13).IsApplicable( + context.get(), transformation_context)); + + // %11 and %13 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(11, 13).IsApplicable( + context.get(), transformation_context)); + + // %13 and %23 are not equivalent + ASSERT_FALSE(TransformationRecordSynonymousConstants(13, 23).IsApplicable( + context.get(), transformation_context)); + + // %13 and %26 are identical float constants + ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 26).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(13, 26, context.get(), + &transformation_context); + + // %9 and %11 are equivalent () + ASSERT_TRUE(TransformationRecordSynonymousConstants(9, 11).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(9, 11, context.get(), + &transformation_context); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/utils/check_copyright.py b/utils/check_copyright.py index 4467a325..39d27cb7 100755 --- a/utils/check_copyright.py +++ b/utils/check_copyright.py @@ -35,7 +35,8 @@ AUTHORS = ['The Khronos Group Inc.', 'Samsung Inc', 'André Perez Maselco', 'Vasyl Teliman', - 'Advanced Micro Devices, Inc.'] + 'Advanced Micro Devices, Inc.', + 'Stefano Milizia'] CURRENT_YEAR='2020' YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020)'