// Copyright (c) 2017 Google Inc. // // 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 <algorithm> #include <cstdarg> #include <iostream> #include <sstream> #include <string> #include <unordered_set> #include <vector> #include "gmock/gmock.h" #include "test/opt/assembly_builder.h" #include "test/opt/pass_fixture.h" #include "test/opt/pass_utils.h" namespace spvtools { namespace opt { namespace { using ::testing::HasSubstr; using ::testing::MatchesRegex; using StrengthReductionBasicTest = PassTest<::testing::Test>; // Test to make sure we replace 5*8. TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) { const std::vector<const char*> text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Vertex %main \"main\"", "OpName %main \"main\"", "%void = OpTypeVoid", "%4 = OpTypeFunction %void", "%uint = OpTypeInt 32 0", "%uint_5 = OpConstant %uint 5", "%uint_8 = OpConstant %uint 8", "%main = OpFunction %void None %4", "%8 = OpLabel", "%9 = OpIMul %uint %uint_5 %uint_8", "OpReturn", "OpFunctionEnd" // clang-format on }; auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); const std::string& output = std::get<0>(result); EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3")); } // TODO(dneto): Add Effcee as required dependency, and make this unconditional. // Test to make sure we replace 16*5 // Also demonstrate use of Effcee matching. TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) { const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %main "main" OpName %main "main" %void = OpTypeVoid %4 = OpTypeFunction %void ; We know disassembly will produce %uint here, but ; CHECK: %uint = OpTypeInt 32 0 ; CHECK-DAG: [[five:%[a-zA-Z_\d]+]] = OpConstant %uint 5 ; We have RE2 regular expressions, so \w matches [_a-zA-Z0-9]. ; This shows the preferred pattern for matching SPIR-V identifiers. ; (We could have cheated in this case since we know the disassembler will ; generate the 'nice' name of "%uint_4". ; CHECK-DAG: [[four:%\w+]] = OpConstant %uint 4 %uint = OpTypeInt 32 0 %uint_5 = OpConstant %uint 5 %uint_16 = OpConstant %uint 16 %main = OpFunction %void None %4 ; CHECK: OpLabel %8 = OpLabel ; CHECK-NEXT: OpShiftLeftLogical %uint [[five]] [[four]] ; The multiplication disappears. ; CHECK-NOT: OpIMul %9 = OpIMul %uint %uint_16 %uint_5 OpReturn ; CHECK: OpFunctionEnd OpFunctionEnd)"; SinglePassRunAndMatch<StrengthReductionPass>(text, false); } // Test to make sure we replace a multiple of 32 and 4. TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) { // In this case, we have two powers of 2. Need to make sure we replace only // one of them for the bit shift. // clang-format off const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %main "main" OpName %main "main" %void = OpTypeVoid %4 = OpTypeFunction %void %int = OpTypeInt 32 1 %int_32 = OpConstant %int 32 %int_4 = OpConstant %int 4 %main = OpFunction %void None %4 %8 = OpLabel %9 = OpIMul %int %int_32 %int_4 OpReturn OpFunctionEnd )"; // clang-format on auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( text, /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); const std::string& output = std::get<0>(result); EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5")); } // Test to make sure we don't replace 0*5. TEST_F(StrengthReductionBasicTest, BasicDontReplace0) { const std::vector<const char*> text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Vertex %main \"main\"", "OpName %main \"main\"", "%void = OpTypeVoid", "%4 = OpTypeFunction %void", "%int = OpTypeInt 32 1", "%int_0 = OpConstant %int 0", "%int_5 = OpConstant %int 5", "%main = OpFunction %void None %4", "%8 = OpLabel", "%9 = OpIMul %int %int_0 %int_5", "OpReturn", "OpFunctionEnd" // clang-format on }; auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); } // Test to make sure we do not replace a multiple of 5 and 7. TEST_F(StrengthReductionBasicTest, BasicNoChange) { const std::vector<const char*> text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Vertex %2 \"main\"", "OpName %2 \"main\"", "%3 = OpTypeVoid", "%4 = OpTypeFunction %3", "%5 = OpTypeInt 32 1", "%6 = OpTypeInt 32 0", "%7 = OpConstant %5 5", "%8 = OpConstant %5 7", "%2 = OpFunction %3 None %4", "%9 = OpLabel", "%10 = OpIMul %5 %7 %8", "OpReturn", "OpFunctionEnd", // clang-format on }; auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); } // Test to make sure constants and types are reused and not duplicated. TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) { const std::vector<const char*> text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Vertex %main \"main\"", "OpName %main \"main\"", "%void = OpTypeVoid", "%4 = OpTypeFunction %void", "%uint = OpTypeInt 32 0", "%uint_8 = OpConstant %uint 8", "%uint_3 = OpConstant %uint 3", "%main = OpFunction %void None %4", "%8 = OpLabel", "%9 = OpIMul %uint %uint_8 %uint_3", "OpReturn", "OpFunctionEnd", // clang-format on }; auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); const std::string& output = std::get<0>(result); EXPECT_THAT(output, Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*"))); EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*"))); } // Test to make sure we generate the constants only once TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) { const std::vector<const char*> text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Vertex %main \"main\"", "OpName %main \"main\"", "%void = OpTypeVoid", "%4 = OpTypeFunction %void", "%uint = OpTypeInt 32 0", "%uint_5 = OpConstant %uint 5", "%uint_9 = OpConstant %uint 9", "%uint_128 = OpConstant %uint 128", "%main = OpFunction %void None %4", "%8 = OpLabel", "%9 = OpIMul %uint %uint_5 %uint_128", "%10 = OpIMul %uint %uint_9 %uint_128", "OpReturn", "OpFunctionEnd" // clang-format on }; auto result = SinglePassRunAndDisassemble<StrengthReductionPass>( JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); const std::string& output = std::get<0>(result); EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7")); EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7")); } // Test to make sure we generate the instructions in the correct position and // that the uses get replaced as well. Here we check that the use in the return // is replaced, we also check that we can replace two OpIMuls when one feeds the // other. TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) { // This is just the preamble to set up the test. const std::vector<const char*> common_text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Fragment %main \"main\" %gl_FragColor", "OpExecutionMode %main OriginUpperLeft", "OpName %main \"main\"", "OpName %foo_i1_ \"foo(i1;\"", "OpName %n \"n\"", "OpName %gl_FragColor \"gl_FragColor\"", "OpName %param \"param\"", "OpDecorate %gl_FragColor Location 0", "%void = OpTypeVoid", "%3 = OpTypeFunction %void", "%int = OpTypeInt 32 1", "%_ptr_Function_int = OpTypePointer Function %int", "%8 = OpTypeFunction %int %_ptr_Function_int", "%int_256 = OpConstant %int 256", "%int_2 = OpConstant %int 2", "%float = OpTypeFloat 32", "%v4float = OpTypeVector %float 4", "%_ptr_Output_v4float = OpTypePointer Output %v4float", "%gl_FragColor = OpVariable %_ptr_Output_v4float Output", "%float_1 = OpConstant %float 1", "%int_10 = OpConstant %int 10", "%float_0_375 = OpConstant %float 0.375", "%float_0_75 = OpConstant %float 0.75", "%uint = OpTypeInt 32 0", "%uint_8 = OpConstant %uint 8", "%uint_1 = OpConstant %uint 1", "%main = OpFunction %void None %3", "%5 = OpLabel", "%param = OpVariable %_ptr_Function_int Function", "OpStore %param %int_10", "%26 = OpFunctionCall %int %foo_i1_ %param", "%27 = OpConvertSToF %float %26", "%28 = OpFDiv %float %float_1 %27", "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1", "OpStore %gl_FragColor %31", "OpReturn", "OpFunctionEnd" // clang-format on }; // This is the real test. The two OpIMul should be replaced. The expected // output is in |foo_after|. const std::vector<const char*> foo_before = { // clang-format off "%foo_i1_ = OpFunction %int None %8", "%n = OpFunctionParameter %_ptr_Function_int", "%11 = OpLabel", "%12 = OpLoad %int %n", "%14 = OpIMul %int %12 %int_256", "%16 = OpIMul %int %14 %int_2", "OpReturnValue %16", "OpFunctionEnd", // clang-format on }; const std::vector<const char*> foo_after = { // clang-format off "%foo_i1_ = OpFunction %int None %8", "%n = OpFunctionParameter %_ptr_Function_int", "%11 = OpLabel", "%12 = OpLoad %int %n", "%33 = OpShiftLeftLogical %int %12 %uint_8", "%34 = OpShiftLeftLogical %int %33 %uint_1", "OpReturnValue %34", "OpFunctionEnd", // clang-format on }; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndCheck<StrengthReductionPass>( JoinAllInsts(Concat(common_text, foo_before)), JoinAllInsts(Concat(common_text, foo_after)), /* skip_nop = */ true, /* do_validate = */ true); } // Test that, when the result of an OpIMul instruction has more than 1 use, and // the instruction is replaced, all of the uses of the results are replace with // the new result. TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) { // This is just the preamble to set up the test. const std::vector<const char*> common_text = { // clang-format off "OpCapability Shader", "%1 = OpExtInstImport \"GLSL.std.450\"", "OpMemoryModel Logical GLSL450", "OpEntryPoint Fragment %main \"main\" %gl_FragColor", "OpExecutionMode %main OriginUpperLeft", "OpName %main \"main\"", "OpName %foo_i1_ \"foo(i1;\"", "OpName %n \"n\"", "OpName %gl_FragColor \"gl_FragColor\"", "OpName %param \"param\"", "OpDecorate %gl_FragColor Location 0", "%void = OpTypeVoid", "%3 = OpTypeFunction %void", "%int = OpTypeInt 32 1", "%_ptr_Function_int = OpTypePointer Function %int", "%8 = OpTypeFunction %int %_ptr_Function_int", "%int_256 = OpConstant %int 256", "%int_2 = OpConstant %int 2", "%float = OpTypeFloat 32", "%v4float = OpTypeVector %float 4", "%_ptr_Output_v4float = OpTypePointer Output %v4float", "%gl_FragColor = OpVariable %_ptr_Output_v4float Output", "%float_1 = OpConstant %float 1", "%int_10 = OpConstant %int 10", "%float_0_375 = OpConstant %float 0.375", "%float_0_75 = OpConstant %float 0.75", "%uint = OpTypeInt 32 0", "%uint_8 = OpConstant %uint 8", "%uint_1 = OpConstant %uint 1", "%main = OpFunction %void None %3", "%5 = OpLabel", "%param = OpVariable %_ptr_Function_int Function", "OpStore %param %int_10", "%26 = OpFunctionCall %int %foo_i1_ %param", "%27 = OpConvertSToF %float %26", "%28 = OpFDiv %float %float_1 %27", "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1", "OpStore %gl_FragColor %31", "OpReturn", "OpFunctionEnd" // clang-format on }; // This is the real test. The two OpIMul instructions should be replaced. In // particular, we want to be sure that both uses of %16 are changed to use the // new result. const std::vector<const char*> foo_before = { // clang-format off "%foo_i1_ = OpFunction %int None %8", "%n = OpFunctionParameter %_ptr_Function_int", "%11 = OpLabel", "%12 = OpLoad %int %n", "%14 = OpIMul %int %12 %int_256", "%16 = OpIMul %int %14 %int_2", "%17 = OpIAdd %int %14 %16", "OpReturnValue %17", "OpFunctionEnd", // clang-format on }; const std::vector<const char*> foo_after = { // clang-format off "%foo_i1_ = OpFunction %int None %8", "%n = OpFunctionParameter %_ptr_Function_int", "%11 = OpLabel", "%12 = OpLoad %int %n", "%34 = OpShiftLeftLogical %int %12 %uint_8", "%35 = OpShiftLeftLogical %int %34 %uint_1", "%17 = OpIAdd %int %34 %35", "OpReturnValue %17", "OpFunctionEnd", // clang-format on }; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndCheck<StrengthReductionPass>( JoinAllInsts(Concat(common_text, foo_before)), JoinAllInsts(Concat(common_text, foo_after)), /* skip_nop = */ true, /* do_validate = */ true); } } // namespace } // namespace opt } // namespace spvtools