// 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 #include "gtest/gtest.h" #include "source/opt/ccp_pass.h" #include "test/opt/pass_fixture.h" #include "test/opt/pass_utils.h" namespace spvtools { namespace opt { namespace { using CCPTest = PassTest<::testing::Test>; TEST_F(CCPTest, PropagateThroughPhis) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %x %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %x "x" OpName %outparm "outparm" OpDecorate %x Flat OpDecorate %x Location 0 OpDecorate %outparm Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %bool = OpTypeBool %_ptr_Function_int = OpTypePointer Function %int %int_4 = OpConstant %int 4 %int_3 = OpConstant %int 3 %int_1 = OpConstant %int 1 %_ptr_Input_int = OpTypePointer Input %int %x = OpVariable %_ptr_Input_int Input %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %main = OpFunction %void None %3 %4 = OpLabel %5 = OpLoad %int %x %9 = OpIAdd %int %int_1 %int_3 %6 = OpSGreaterThan %bool %5 %int_3 OpSelectionMerge %25 None OpBranchConditional %6 %22 %23 %22 = OpLabel ; CHECK: OpCopyObject %int %int_4 %7 = OpCopyObject %int %9 OpBranch %25 %23 = OpLabel %8 = OpCopyObject %int %int_4 OpBranch %25 %25 = OpLabel ; %int_4 should have propagated to both OpPhi operands. ; CHECK: OpPhi %int %int_4 {{%\d+}} %int_4 {{%\d+}} %35 = OpPhi %int %7 %22 %8 %23 ; This function always returns 4. DCE should get rid of everything else. ; CHECK OpStore %outparm %int_4 OpStore %outparm %35 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, SimplifyConditionals) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %outparm "outparm" OpDecorate %outparm Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %bool = OpTypeBool %_ptr_Function_int = OpTypePointer Function %int %int_4 = OpConstant %int 4 %int_3 = OpConstant %int 3 %int_1 = OpConstant %int 1 %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %main = OpFunction %void None %3 %4 = OpLabel %9 = OpIAdd %int %int_4 %int_3 %6 = OpSGreaterThan %bool %9 %int_3 OpSelectionMerge %25 None ; CHECK: OpBranchConditional %true [[bb_taken:%\d+]] [[bb_not_taken:%\d+]] OpBranchConditional %6 %22 %23 ; CHECK: [[bb_taken]] = OpLabel %22 = OpLabel ; CHECK: OpCopyObject %int %int_7 %7 = OpCopyObject %int %9 OpBranch %25 ; CHECK: [[bb_not_taken]] = OpLabel %23 = OpLabel ; CHECK: [[id_not_evaluated:%\d+]] = OpCopyObject %int %int_4 %8 = OpCopyObject %int %int_4 OpBranch %25 %25 = OpLabel ; %int_7 should have propagated to the first OpPhi operand. But the else branch ; is not executable (conditional is always true), so no values should be ; propagated there and the value of the OpPhi should always be %int_7. ; CHECK: OpPhi %int %int_7 [[bb_taken]] [[id_not_evaluated]] [[bb_not_taken]] %35 = OpPhi %int %7 %22 %8 %23 ; Only the true path of the conditional is ever executed. The output of this ; function is always %int_7. ; CHECK: OpStore %outparm %int_7 OpStore %outparm %35 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, SimplifySwitches) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %outparm "outparm" OpDecorate %outparm Location 0 %void = OpTypeVoid %6 = OpTypeFunction %void %int = OpTypeInt 32 1 %_ptr_Function_int = OpTypePointer Function %int %int_23 = OpConstant %int 23 %int_42 = OpConstant %int 42 %int_14 = OpConstant %int 14 %int_15 = OpConstant %int 15 %int_4 = OpConstant %int 4 %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %main = OpFunction %void None %6 %15 = OpLabel OpSelectionMerge %17 None OpSwitch %int_23 %17 10 %18 13 %19 23 %20 %18 = OpLabel OpBranch %17 %19 = OpLabel OpBranch %17 %20 = OpLabel OpBranch %17 %17 = OpLabel %24 = OpPhi %int %int_23 %15 %int_42 %18 %int_14 %19 %int_15 %20 ; The switch will always jump to label %20, which carries the value %int_15. ; CHECK: OpIAdd %int %int_15 %int_4 %22 = OpIAdd %int %24 %int_4 ; Consequently, the return value will always be %int_19. ; CHECK: OpStore %outparm %int_19 OpStore %outparm %22 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, SimplifySwitchesDefaultBranch) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %outparm "outparm" OpDecorate %outparm Location 0 %void = OpTypeVoid %6 = OpTypeFunction %void %int = OpTypeInt 32 1 %_ptr_Function_int = OpTypePointer Function %int %int_42 = OpConstant %int 42 %int_4 = OpConstant %int 4 %int_1 = OpConstant %int 1 %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %main = OpFunction %void None %6 %13 = OpLabel %15 = OpIAdd %int %int_42 %int_4 OpSelectionMerge %16 None ; CHECK: OpSwitch %int_46 {{%\d+}} 10 {{%\d+}} OpSwitch %15 %17 10 %18 %18 = OpLabel OpBranch %16 %17 = OpLabel OpBranch %16 %16 = OpLabel %22 = OpPhi %int %int_42 %18 %int_1 %17 ; The switch will always jump to the default label %17. This carries the value ; %int_1. ; CHECK: OpIAdd %int %int_1 %int_4 %20 = OpIAdd %int %22 %int_4 ; Resulting in a return value of %int_5. ; CHECK: OpStore %outparm %int_5 OpStore %outparm %20 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, SimplifyIntVector) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %OutColor OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %v "v" OpName %OutColor "OutColor" OpDecorate %OutColor Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %v4int = OpTypeVector %int 4 %_ptr_Function_v4int = OpTypePointer Function %v4int %int_1 = OpConstant %int 1 %int_2 = OpConstant %int 2 %int_3 = OpConstant %int 3 %int_4 = OpConstant %int 4 %14 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4 %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %_ptr_Function_int = OpTypePointer Function %int %_ptr_Output_v4int = OpTypePointer Output %v4int %OutColor = OpVariable %_ptr_Output_v4int Output %main = OpFunction %void None %3 %5 = OpLabel %v = OpVariable %_ptr_Function_v4int Function OpStore %v %14 %18 = OpAccessChain %_ptr_Function_int %v %uint_0 %19 = OpLoad %int %18 ; The constant folder does not see through access chains. To get this, the ; vector would have to be scalarized. ; CHECK: [[result_id:%\d+]] = OpIAdd %int {{%\d+}} %int_1 %20 = OpIAdd %int %19 %int_1 %21 = OpAccessChain %_ptr_Function_int %v %uint_0 ; CHECK: OpStore {{%\d+}} [[result_id]] OpStore %21 %20 %24 = OpLoad %v4int %v OpStore %OutColor %24 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, BadSimplifyFloatVector) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %OutColor OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %v "v" OpName %OutColor "OutColor" OpDecorate %OutColor Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %float = OpTypeFloat 32 %v4float = OpTypeVector %float 4 %_ptr_Function_v4float = OpTypePointer Function %v4float %float_1 = OpConstant %float 1 %float_2 = OpConstant %float 2 %float_3 = OpConstant %float 3 %float_4 = OpConstant %float 4 %14 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4 %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %_ptr_Function_float = OpTypePointer Function %float %_ptr_Output_v4float = OpTypePointer Output %v4float %OutColor = OpVariable %_ptr_Output_v4float Output %main = OpFunction %void None %3 %5 = OpLabel %v = OpVariable %_ptr_Function_v4float Function OpStore %v %14 %18 = OpAccessChain %_ptr_Function_float %v %uint_0 %19 = OpLoad %float %18 ; NOTE: This test should start failing once floating point folding is ; implemented (https://github.com/KhronosGroup/SPIRV-Tools/issues/943). ; This should be checking that we are adding %float_1 + %float_1. ; CHECK: [[result_id:%\d+]] = OpFAdd %float {{%\d+}} %float_1 %20 = OpFAdd %float %19 %float_1 %21 = OpAccessChain %_ptr_Function_float %v %uint_0 ; This should be checkint that we are storing %float_2 instead of result_it. ; CHECK: OpStore {{%\d+}} [[result_id]] OpStore %21 %20 %24 = OpLoad %v4float %v OpStore %OutColor %24 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, NoLoadStorePropagation) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" OpName %x "x" OpName %outparm "outparm" OpDecorate %outparm Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %_ptr_Function_int = OpTypePointer Function %int %int_23 = OpConstant %int 23 %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %main = OpFunction %void None %3 %5 = OpLabel %x = OpVariable %_ptr_Function_int Function OpStore %x %int_23 ; int_23 should not propagate into this load. ; CHECK: [[load_id:%\d+]] = OpLoad %int %x %12 = OpLoad %int %x ; Nor into this copy operation. ; CHECK: [[copy_id:%\d+]] = OpCopyObject %int [[load_id]] %13 = OpCopyObject %int %12 ; Likewise here. ; CHECK: OpStore %outparm [[copy_id]] OpStore %outparm %13 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, HandleAbortInstructions) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft OpSource HLSL 500 OpName %main "main" %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %bool = OpTypeBool ; CHECK: %true = OpConstantTrue %bool %int_3 = OpConstant %int 3 %int_1 = OpConstant %int 1 %main = OpFunction %void None %3 %4 = OpLabel %9 = OpIAdd %int %int_3 %int_1 %6 = OpSGreaterThan %bool %9 %int_3 OpSelectionMerge %23 None ; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}} OpBranchConditional %6 %22 %23 %22 = OpLabel OpKill %23 = OpLabel OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, SSAWebCycles) { // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1159 // When there is a cycle in the SSA def-use web, the propagator was getting // into an infinite loop. SSA edges for Phi instructions should not be // added to the edges to simulate. const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 OpName %main "main" %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %_ptr_Function_int = OpTypePointer Function %int %int_0 = OpConstant %int 0 %int_4 = OpConstant %int 4 %bool = OpTypeBool %int_1 = OpConstant %int 1 %_ptr_Output_int = OpTypePointer Output %int %main = OpFunction %void None %3 %5 = OpLabel OpBranch %11 %11 = OpLabel %29 = OpPhi %int %int_0 %5 %22 %14 %30 = OpPhi %int %int_0 %5 %25 %14 OpLoopMerge %13 %14 None OpBranch %15 %15 = OpLabel %19 = OpSLessThan %bool %30 %int_4 ; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}} OpBranchConditional %19 %12 %13 %12 = OpLabel ; CHECK: OpIAdd %int %int_0 %int_0 %22 = OpIAdd %int %29 %30 OpBranch %14 %14 = OpLabel ; CHECK: OpPhi %int %int_0 {{%\d+}} %25 = OpPhi %int %30 %12 OpBranch %11 %13 = OpLabel OpReturn OpFunctionEnd )"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, LoopInductionVariables) { // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1143 // We are failing to properly consider the induction variable for this loop // as Varying. const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft OpSource GLSL 430 OpName %main "main" %void = OpTypeVoid %5 = OpTypeFunction %void %int = OpTypeInt 32 1 %_ptr_Function_int = OpTypePointer Function %int %int_0 = OpConstant %int 0 %int_10 = OpConstant %int 10 %bool = OpTypeBool %int_1 = OpConstant %int 1 %main = OpFunction %void None %5 %12 = OpLabel OpBranch %13 %13 = OpLabel ; This Phi should not have all constant arguments: ; CHECK: [[phi_id:%\d+]] = OpPhi %int %int_0 {{%\d+}} {{%\d+}} {{%\d+}} %22 = OpPhi %int %int_0 %12 %21 %15 OpLoopMerge %14 %15 None OpBranch %16 %16 = OpLabel ; The Phi should never be considered to have the value %int_0. ; CHECK: [[branch_selector:%\d+]] = OpSLessThan %bool [[phi_id]] %int_10 %18 = OpSLessThan %bool %22 %int_10 ; This conditional was wrongly converted into an always-true jump due to the ; bad meet evaluation of %22. ; CHECK: OpBranchConditional [[branch_selector]] {{%\d+}} {{%\d+}} OpBranchConditional %18 %19 %14 %19 = OpLabel OpBranch %15 %15 = OpLabel ; CHECK: OpIAdd %int [[phi_id]] %int_1 %21 = OpIAdd %int %22 %int_1 OpBranch %13 %14 = OpLabel OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(spv_asm, true); } TEST_F(CCPTest, HandleCompositeWithUndef) { // Check to make sure that CCP does not crash when given a "constant" struct // with an undef. If at a later time CCP is enhanced to optimize this case, // it is not wrong. const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft OpSource HLSL 500 OpName %main "main" %void = OpTypeVoid %4 = OpTypeFunction %void %int = OpTypeInt 32 1 %bool = OpTypeBool %_struct_7 = OpTypeStruct %int %int %int_1 = OpConstant %int 1 %9 = OpUndef %int %10 = OpConstantComposite %_struct_7 %int_1 %9 %main = OpFunction %void None %4 %11 = OpLabel %12 = OpCompositeExtract %int %10 0 %13 = OpCopyObject %int %12 OpReturn OpFunctionEnd )"; auto res = SinglePassRunToBinary(spv_asm, true); EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange); } TEST_F(CCPTest, SkipSpecConstantInstrucitons) { const std::string spv_asm = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" OpExecutionMode %main OriginUpperLeft OpSource HLSL 500 OpName %main "main" %void = OpTypeVoid %4 = OpTypeFunction %void %bool = OpTypeBool %10 = OpSpecConstantFalse %bool %main = OpFunction %void None %4 %11 = OpLabel OpBranchConditional %10 %L1 %L2 %L1 = OpLabel OpReturn %L2 = OpLabel OpReturn OpFunctionEnd )"; auto res = SinglePassRunToBinary(spv_asm, true); EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange); } TEST_F(CCPTest, FoldConstantCompositeInstrucitonsWithSpecConst) { const std::string spv_asm = R"( OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %1 "main" OpExecutionMode %1 OriginUpperLeft %void = OpTypeVoid %4 = OpTypeFunction %void %bool = OpTypeBool %v3bool = OpTypeVector %bool 3 %_struct_8 = OpTypeStruct %v3bool %true = OpConstantTrue %bool ; CHECK: [[spec_const:%\w+]] = OpSpecConstantComposite %v3bool %11 = OpSpecConstantComposite %v3bool %true %true %true %12 = OpConstantComposite %_struct_8 %11 ; CHECK: OpFunction %1 = OpFunction %void None %4 %29 = OpLabel %31 = OpCompositeExtract %v3bool %12 0 ; CHECK: OpCompositeExtract %bool [[spec_const]] 0 %32 = OpCompositeExtract %bool %31 0 OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(spv_asm, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } TEST_F(CCPTest, UpdateSubsequentPhisToVarying) { const std::string text = R"( OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" %in OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %bool = OpTypeBool %int = OpTypeInt 32 1 %false = OpConstantFalse %bool %int0 = OpConstant %int 0 %int1 = OpConstant %int 1 %int6 = OpConstant %int 6 %int_ptr_Input = OpTypePointer Input %int %in = OpVariable %int_ptr_Input Input %undef = OpUndef %int ; Although no constants are propagated in this function, the propagator ; generates a new %true value while visiting conditional statements. ; CHECK: %true = OpConstantTrue %bool %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpBranch %2 %2 = OpLabel %outer_phi = OpPhi %int %int0 %1 %outer_add %15 %cond1 = OpSLessThanEqual %bool %outer_phi %int6 OpLoopMerge %3 %15 None OpBranchConditional %cond1 %4 %3 %4 = OpLabel %ld = OpLoad %int %in %cond2 = OpSGreaterThanEqual %bool %int1 %ld OpSelectionMerge %10 None OpBranchConditional %cond2 %8 %9 %8 = OpLabel OpBranch %10 %9 = OpLabel OpBranch %10 %10 = OpLabel %extra_phi = OpPhi %int %outer_phi %8 %outer_phi %9 OpBranch %11 %11 = OpLabel %inner_phi = OpPhi %int %int0 %10 %inner_add %13 %cond3 = OpSLessThanEqual %bool %inner_phi %int6 OpLoopMerge %14 %13 None OpBranchConditional %cond3 %12 %14 %12 = OpLabel OpBranch %13 %13 = OpLabel %inner_add = OpIAdd %int %inner_phi %int1 OpBranch %11 %14 = OpLabel OpBranch %15 %15 = OpLabel %outer_add = OpIAdd %int %extra_phi %int1 OpBranch %2 %3 = OpLabel OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } TEST_F(CCPTest, UndefInPhi) { const std::string text = R"( ; CHECK: [[uint1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: [[phi:%\w+]] = OpPhi ; CHECK: OpIAdd {{%\w+}} [[phi]] [[uint1]] OpCapability Kernel OpCapability Linkage OpMemoryModel Logical OpenCL OpDecorate %1 LinkageAttributes "func" Export %void = OpTypeVoid %bool = OpTypeBool %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %7 = OpUndef %uint %8 = OpTypeFunction %void %bool %1 = OpFunction %void None %8 %9 = OpFunctionParameter %bool %10 = OpLabel OpBranchConditional %9 %11 %12 %11 = OpLabel OpBranch %13 %12 = OpLabel OpBranch %14 %14 = OpLabel OpBranchConditional %9 %13 %15 %15 = OpLabel OpBranch %13 %13 = OpLabel %16 = OpPhi %uint %uint_0 %11 %7 %14 %uint_1 %15 %17 = OpIAdd %uint %16 %uint_1 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } // Just test to make sure the constant fold rules are being used. Will rely on // the folding test for specific testing of specific rules. TEST_F(CCPTest, UseConstantFoldingRules) { const std::string text = R"( ; CHECK: [[float1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: OpReturnValue [[float1]] OpCapability Shader OpCapability Linkage OpMemoryModel Logical GLSL450 OpDecorate %1 LinkageAttributes "func" Export %void = OpTypeVoid %bool = OpTypeBool %float = OpTypeFloat 32 %float_0 = OpConstant %float 0 %float_1 = OpConstant %float 1 %8 = OpTypeFunction %float %1 = OpFunction %float None %8 %10 = OpLabel %17 = OpFAdd %float %float_0 %float_1 OpReturnValue %17 OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } // Test for #1300. Previously value for %5 would not settle during simulation. TEST_F(CCPTest, SettlePhiLatticeValue) { const std::string text = R"( OpCapability Kernel OpCapability Linkage OpMemoryModel Logical OpenCL OpDecorate %func LinkageAttributes "func" Export %void = OpTypeVoid %bool = OpTypeBool %true = OpConstantTrue %bool %false = OpConstantFalse %bool %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpBranchConditional %true %2 %3 %3 = OpLabel OpBranch %2 %2 = OpLabel %5 = OpPhi %bool %true %1 %false %3 OpReturn OpFunctionEnd )"; SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunToBinary(text, true); } TEST_F(CCPTest, NullBranchCondition) { const std::string text = R"( ; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2 ; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]] OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %bool = OpTypeBool %int = OpTypeInt 32 1 %null = OpConstantNull %bool %int_1 = OpConstant %int 1 %int_2 = OpConstant %int 2 %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpSelectionMerge %2 None OpBranchConditional %null %2 %3 %3 = OpLabel OpBranch %2 %2 = OpLabel %phi = OpPhi %int %int_1 %1 %int_2 %3 %add = OpIAdd %int %int_1 %phi OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, UndefBranchCondition) { const std::string text = R"( ; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: [[phi:%\w+]] = OpPhi ; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]] OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %bool = OpTypeBool %int = OpTypeInt 32 1 %undef = OpUndef %bool %int_1 = OpConstant %int 1 %int_2 = OpConstant %int 2 %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpSelectionMerge %2 None OpBranchConditional %undef %2 %3 %3 = OpLabel OpBranch %2 %2 = OpLabel %phi = OpPhi %int %int_1 %1 %int_2 %3 %add = OpIAdd %int %int_1 %phi OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, NullSwitchCondition) { const std::string text = R"( ; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2 ; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]] OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %int = OpTypeInt 32 1 %null = OpConstantNull %int %int_1 = OpConstant %int 1 %int_2 = OpConstant %int 2 %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpSelectionMerge %2 None OpSwitch %null %2 0 %3 %3 = OpLabel OpBranch %2 %2 = OpLabel %phi = OpPhi %int %int_1 %1 %int_2 %3 %add = OpIAdd %int %int_1 %phi OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, UndefSwitchCondition) { const std::string text = R"( ; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 ; CHECK: [[phi:%\w+]] = OpPhi ; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]] OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %int = OpTypeInt 32 1 %undef = OpUndef %int %int_1 = OpConstant %int 1 %int_2 = OpConstant %int 2 %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel OpSelectionMerge %2 None OpSwitch %undef %2 0 %3 %3 = OpLabel OpBranch %2 %2 = OpLabel %phi = OpPhi %int %int_1 %1 %int_2 %3 %add = OpIAdd %int %int_1 %phi OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } // Test for #1361. TEST_F(CCPTest, CompositeConstructOfGlobalValue) { const std::string text = R"( ; CHECK: [[phi:%\w+]] = OpPhi ; CHECK-NEXT: OpCompositeExtract {{%\w+}} [[phi]] 0 OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" %in OpExecutionMode %func OriginUpperLeft %void = OpTypeVoid %int = OpTypeInt 32 1 %bool = OpTypeBool %functy = OpTypeFunction %void %ptr_int_Input = OpTypePointer Input %int %in = OpVariable %ptr_int_Input Input %struct = OpTypeStruct %ptr_int_Input %ptr_int_Input %struct_null = OpConstantNull %struct %func = OpFunction %void None %functy %1 = OpLabel OpBranch %2 %2 = OpLabel %phi = OpPhi %struct %struct_null %1 %5 %4 %extract = OpCompositeExtract %ptr_int_Input %phi 0 OpLoopMerge %3 %4 None OpBranch %4 %4 = OpLabel %5 = OpCompositeConstruct %struct %in %in OpBranch %2 %3 = OpLabel OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, FoldWithDecoration) { const std::string text = R"( ; CHECK: OpCapability ; CHECK-NOT: OpDecorate ; CHECK: OpFunctionEnd OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource ESSL 310 OpDecorate %3 RelaxedPrecision %void = OpTypeVoid %5 = OpTypeFunction %void %float = OpTypeFloat 32 %v3float = OpTypeVector %float 3 %float_0 = OpConstant %float 0 %v4float = OpTypeVector %float 4 %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 %2 = OpFunction %void None %5 %11 = OpLabel %3 = OpVectorShuffle %v3float %10 %10 0 1 2 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, DebugSimpleFoldConstant) { const std::string text = R"( OpCapability Shader OpCapability Linkage %ext = OpExtInstImport "OpenCL.DebugInfo.100" OpMemoryModel Logical GLSL450 %file_name = OpString "test" %float_name = OpString "float" %main_name = OpString "main" %f_name = OpString "f" OpDecorate %1 LinkageAttributes "func" Export %void = OpTypeVoid %bool = OpTypeBool %float = OpTypeFloat 32 %float_0 = OpConstant %float 0 ; CHECK: [[float1:%\w+]] = OpConstant {{%\w+}} 1 %float_1 = OpConstant %float 1 %uint = OpTypeInt 32 0 %uint_32 = OpConstant %uint 32 %8 = OpTypeFunction %float %null_expr = OpExtInst %void %ext DebugExpression %src = OpExtInst %void %ext DebugSource %file_name %cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL %dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float %main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_tf %dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %1 %dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_tf %src 0 0 %dbg_main FlagIsLocal %1 = OpFunction %float None %8 %10 = OpLabel ; CHECK: OpExtInst %void [[ext:%\w+]] DebugScope ; CHECK: OpLine [[file:%\w+]] 1 0 ; CHECK: OpExtInst %void [[ext]] DebugValue {{%\w+}} %float_1 %s0 = OpExtInst %void %ext DebugScope %dbg_main OpLine %file_name 1 0 %17 = OpFAdd %float %float_0 %float_1 %val = OpExtInst %void %ext DebugValue %dbg_f %17 %null_expr ; CHECK: OpLine [[file]] 2 0 ; CHECK: OpReturnValue [[float1]] OpLine %file_name 2 0 OpReturnValue %17 OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } TEST_F(CCPTest, DebugFoldMultipleForSingleConstant) { const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" %ext = OpExtInstImport "OpenCL.DebugInfo.100" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %outparm OpExecutionMode %main OriginUpperLeft OpSource GLSL 450 %file_name = OpString "test" %float_name = OpString "float" %main_name = OpString "main" %f_name = OpString "f" OpName %main "main" OpName %outparm "outparm" OpDecorate %outparm Location 0 %void = OpTypeVoid %3 = OpTypeFunction %void %int = OpTypeInt 32 1 %bool = OpTypeBool %_ptr_Function_int = OpTypePointer Function %int %int_4 = OpConstant %int 4 %int_3 = OpConstant %int 3 %int_1 = OpConstant %int 1 %uint = OpTypeInt 32 0 %uint_32 = OpConstant %uint 32 %_ptr_Output_int = OpTypePointer Output %int %outparm = OpVariable %_ptr_Output_int Output %null_expr = OpExtInst %void %ext DebugExpression %src = OpExtInst %void %ext DebugSource %file_name %cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL %dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float %main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_tf %dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %main %bb0 = OpExtInst %void %ext DebugLexicalBlock %src 0 0 %dbg_main %bb1 = OpExtInst %void %ext DebugLexicalBlock %src 1 0 %dbg_main %bb2 = OpExtInst %void %ext DebugLexicalBlock %src 2 0 %dbg_main %bb3 = OpExtInst %void %ext DebugLexicalBlock %src 3 0 %dbg_main %dbg_f0 = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_tf %src 0 0 %dbg_main FlagIsLocal %dbg_f1 = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_tf %src 1 0 %dbg_main FlagIsLocal %dbg_f2 = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_tf %src 2 0 %dbg_main FlagIsLocal %main = OpFunction %void None %3 %4 = OpLabel ; CHECK: OpExtInst %void [[ext:%\w+]] DebugScope ; CHECK: OpLine [[file:%\w+]] 1 0 ; CHECK: OpIAdd %int %int_4 %int_3 ; CHECK: OpExtInst %void [[ext]] DebugValue {{%\w+}} %int_7 %s0 = OpExtInst %void %ext DebugScope %bb0 OpLine %file_name 1 0 %9 = OpIAdd %int %int_4 %int_3 %val0 = OpExtInst %void %ext DebugValue %dbg_f0 %9 %null_expr ; CHECK: OpLine [[file]] 2 0 ; CHECK: OpSGreaterThan %bool %int_7 %int_3 ; CHECK: OpExtInst %void [[ext]] DebugValue {{%\w+}} %true OpLine %file_name 2 0 %6 = OpSGreaterThan %bool %9 %int_3 %val1 = OpExtInst %void %ext DebugValue %dbg_f1 %6 %null_expr OpSelectionMerge %25 None OpBranchConditional %6 %22 %23 %22 = OpLabel %s1 = OpExtInst %void %ext DebugScope %bb1 %7 = OpCopyObject %int %9 %val2 = OpExtInst %void %ext DebugValue %dbg_f2 %7 %null_expr OpBranch %25 %23 = OpLabel %s2 = OpExtInst %void %ext DebugScope %bb2 %8 = OpCopyObject %int %int_4 OpBranch %25 %25 = OpLabel %s3 = OpExtInst %void %ext DebugScope %bb3 %35 = OpPhi %int %7 %22 %8 %23 OpStore %outparm %35 OpReturn OpFunctionEnd )"; SinglePassRunAndMatch(text, true); } // Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 TEST_F(CCPTest, CCPNoChangeFailure) { const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 320 %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpConstant %6 2 %13 = OpConstant %6 4 %21 = OpConstant %6 1 %10 = OpTypeBool %17 = OpTypePointer Function %6 ; CCP is generating two new constants during propagation that end up being ; dead because they cannot be replaced anywhere in the IR. CCP was wrongly ; considering the IR to be unmodified because of this. ; CHECK: %true = OpConstantTrue %bool ; CHECK: %int_3 = OpConstant %int 3 %4 = OpFunction %2 None %3 %11 = OpLabel OpBranch %5 %5 = OpLabel %23 = OpPhi %6 %7 %11 %20 %15 %9 = OpSLessThan %10 %23 %13 OpLoopMerge %8 %15 None OpBranchConditional %9 %15 %8 %15 = OpLabel %20 = OpIAdd %6 %23 %21 OpBranch %5 %8 = OpLabel OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } // Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3738 // Similar to the previous one but more than one constant is generated in a // single call to the instruction folder. TEST_F(CCPTest, CCPNoChangeFailureSeveralConstantsDuringFolding) { const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft %void = OpTypeVoid %4 = OpTypeFunction %void %float = OpTypeFloat 32 %v3float = OpTypeVector %float 3 %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %bool = OpTypeBool %v3bool = OpTypeVector %bool 3 %float_0 = OpConstant %float 0 %12 = OpConstantComposite %v3float %float_0 %float_0 %float_0 %float_0_300000012 = OpConstant %float 0.300000012 %14 = OpConstantComposite %v3float %float_0_300000012 %float_0_300000012 %float_0_300000012 ; CCP is generating several constants during a single instruction evaluation. ; When folding %19, it generates the constants %true and %24. They are dead ; because they cannot be replaced anywhere in the IR. CCP was wrongly ; considering the IR to be unmodified because of this. ; ; CHECK: %true = OpConstantTrue %bool ; CHECK: %24 = OpConstantComposite %v3bool %true %true %true ; CHECK: %float_1 = OpConstant %float 1 ; CHECK: %float_0_699999988 = OpConstant %float 0.699999988 %2 = OpFunction %void None %4 %15 = OpLabel OpBranch %16 %16 = OpLabel %17 = OpPhi %v3float %12 %15 %14 %18 %19 = OpFOrdLessThan %v3bool %17 %14 %20 = OpAll %bool %19 OpLoopMerge %21 %18 None OpBranchConditional %20 %18 %21 %18 = OpLabel OpBranch %16 %21 = OpLabel %22 = OpExtInst %v3float %1 FMix %12 %17 %14 OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } // Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3991 // Similar to the previous one but constants are created even when no // instruction are ever folded during propagation. TEST_F(CCPTest, CCPNoChangeFailureWithUnfoldableInstr) { const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft %void = OpTypeVoid %4 = OpTypeFunction %void %float = OpTypeFloat 32 %v3float = OpTypeVector %float 3 %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %bool = OpTypeBool %float_0 = OpConstant %float 0 %11 = OpConstantComposite %v3float %float_0 %float_0 %float_0 %float_0_300000012 = OpConstant %float 0.300000012 %13 = OpConstantComposite %v3float %float_0_300000012 %float_0_300000012 %float_0_300000012 ; CCP generates two constants when trying to fold an instruction, which it ; ultimately fails to fold. The instruction folder in CCP was only ; checking for newly added constants if the instruction folds successfully. ; ; CHECK: %float_1 = OpConstant %float 1 ; CHECK: %float_0_699999988 = OpConstant %float 0.69999998 %2 = OpFunction %void None %4 %14 = OpLabel %15 = OpBitcast %uint %float_0_300000012 %16 = OpUGreaterThan %bool %15 %uint_0 OpBranch %17 %17 = OpLabel %18 = OpPhi %v3float %11 %14 %13 %19 OpLoopMerge %20 %19 None OpBranchConditional %16 %19 %20 %19 = OpLabel OpBranch %17 %20 = OpLabel %21 = OpExtInst %v3float %1 FMix %11 %18 %13 OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } TEST_F(CCPTest, FunctionDeclaration) { // Make sure the pass works with a function declaration that is called. const std::string text = R"(OpCapability Addresses OpCapability Linkage OpCapability Kernel OpCapability Int8 %1 = OpExtInstImport "OpenCL.std" OpMemoryModel Physical64 OpenCL OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" OpExecutionMode %2 ContractionOff OpSource Unknown 0 OpDecorate %3 LinkageAttributes "julia_error_7712" Import %void = OpTypeVoid %5 = OpTypeFunction %void %3 = OpFunction %void None %5 OpFunctionEnd %2 = OpFunction %void None %5 %6 = OpLabel %7 = OpFunctionCall %void %3 OpReturn OpFunctionEnd )"; SinglePassRunAndCheck(text, text, false); } // Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/4462. // The test was causing a lateral movement in the constant lattice, which was // not being detected as varying by CCP. In this test, FClamp is evaluated // twice. On the first evaluation, if computes FClamp(0.5, 0.5, -1) which // returns -1. On the second evaluation, it computes FClamp(0.5, 0.5, VARYING) // which returns 0.5. // // Both fold() computations are correct given the semantics of FClamp() but // this causes a lateral transition in the constant lattice which was not being // considered VARYING by CCP. TEST_F(CCPTest, LateralLatticeTransition) { const std::string text = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %gl_FragCoord %outColor OpExecutionMode %main OriginUpperLeft OpSource ESSL 310 OpName %main "main" OpName %gl_FragCoord "gl_FragCoord" OpName %outColor "outColor" OpDecorate %gl_FragCoord BuiltIn FragCoord OpDecorate %outColor Location 0 %void = OpTypeVoid %6 = OpTypeFunction %void %float = OpTypeFloat 32 %float_0_5 = OpConstant %float 0.5 %v4float = OpTypeVector %float 4 %_ptr_Input_v4float = OpTypePointer Input %v4float %gl_FragCoord = OpVariable %_ptr_Input_v4float Input %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %_ptr_Input_float = OpTypePointer Input %float %float_0 = OpConstant %float 0 %bool = OpTypeBool %float_n1 = OpConstant %float -1 %float_1 = OpConstant %float 1 %_ptr_Output_v4float = OpTypePointer Output %v4float %outColor = OpVariable %_ptr_Output_v4float Output ; This constant is created during the first evaluation of the CompositeConstruct ; CHECK: [[new_constant:%\d+]] = OpConstantComposite %v4float %float_n1 %float_0_5 %float_0 %float_1 %main = OpFunction %void None %6 %19 = OpLabel %20 = OpAccessChain %_ptr_Input_float %gl_FragCoord %uint_0 %21 = OpLoad %float %20 %22 = OpFOrdLessThan %bool %21 %float_0 OpSelectionMerge %23 None OpBranchConditional %22 %24 %25 %24 = OpLabel OpBranch %23 %25 = OpLabel OpBranch %26 %26 = OpLabel OpBranch %23 %23 = OpLabel %27 = OpPhi %float %float_n1 %24 %float_0_5 %26 %28 = OpExtInst %float %1 FClamp %float_0_5 %float_0_5 %27 ; On first evaluation, the result from FClamp will return 0.5. ; But on second evaluation, FClamp should return VARYING. Check ; that CCP is not keeping the first result. ; CHECK-NOT: %29 = OpCompositeConstruct %v4float %float_0_5 %float_0_5 %float_0 %float_1 %29 = OpCompositeConstruct %v4float %28 %float_0_5 %float_0 %float_1 ; CHECK-NOT: OpCopyObject %v4float [[new_constant]] %42 = OpCopyObject %v4float %29 ; CHECK-NOT: OpStore %outColor [[new_constant]] OpStore %outColor %42 OpReturn OpFunctionEnd )"; auto result = SinglePassRunAndMatch(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } } // namespace } // namespace opt } // namespace spvtools