| // Copyright 2025 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "src/tint/lang/spirv/reader/parser/helper_test.h" |
| |
| namespace tint::spirv::reader { |
| namespace { |
| |
| TEST_F(SpirvParserTest, Phi_FromBlock) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %int_2 = OpConstant %int 2 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| %2 = OpPhi %int %int_2 %main_start |
| %3 = OpIAdd %int %2 %2 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = spirv.add<i32> 2i, 2i |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_If_Undef) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| OpSelectionMerge %5 None |
| OpBranchConditional %true %3 %4 |
| %3 = OpLabel |
| %6 = OpUndef %int |
| OpBranch %5 |
| %4 = OpLabel |
| %7 = OpUndef %int |
| OpBranch %5 |
| %5 = OpLabel |
| %8 = OpPhi %int %6 %3 %7 %4 |
| %9 = OpIAdd %int %8 %8 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| exit_if 0i # if_1 |
| } |
| $B3: { # false |
| exit_if 0i # if_1 |
| } |
| } |
| %3:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_If_ThenAndElse) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %int_3 = OpConstant %int 3 |
| %int_4 = OpConstant %int 4 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| OpSelectionMerge %5 None |
| OpBranchConditional %true %3 %4 |
| %3 = OpLabel |
| %6 = OpIAdd %int %int_1 %int_2 |
| OpBranch %5 |
| %4 = OpLabel |
| %7 = OpIAdd %int %int_3 %int_4 |
| OpBranch %5 |
| %5 = OpLabel |
| %8 = OpPhi %int %6 %3 %7 %4 |
| %9 = OpIAdd %int %8 %8 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| %3:i32 = spirv.add<i32> 1i, 2i |
| exit_if %3 # if_1 |
| } |
| $B3: { # false |
| %4:i32 = spirv.add<i32> 3i, 4i |
| exit_if %4 # if_1 |
| } |
| } |
| %5:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_If_ThenNoElse) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %int_3 = OpConstant %int 3 |
| %int_4 = OpConstant %int 4 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| OpSelectionMerge %5 None |
| OpBranchConditional %true %3 %5 |
| %3 = OpLabel |
| %6 = OpIAdd %int %int_1 %int_2 |
| OpBranch %5 |
| %5 = OpLabel |
| %8 = OpPhi %int %int_2 %1 %6 %3 |
| %9 = OpIAdd %int %8 %8 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| %3:i32 = spirv.add<i32> 1i, 2i |
| exit_if %3 # if_1 |
| } |
| $B3: { # false |
| exit_if 2i # if_1 |
| } |
| } |
| %4:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_If_NoThenElse) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %int_3 = OpConstant %int 3 |
| %int_4 = OpConstant %int 4 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| OpSelectionMerge %5 None |
| OpBranchConditional %true %5 %3 |
| %3 = OpLabel |
| %6 = OpIAdd %int %int_1 %int_2 |
| OpBranch %5 |
| %5 = OpLabel |
| %8 = OpPhi %int %6 %3 %int_2 %1 |
| %9 = OpIAdd %int %8 %8 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| exit_if 2i # if_1 |
| } |
| $B3: { # false |
| %3:i32 = spirv.add<i32> 1i, 2i |
| exit_if %3 # if_1 |
| } |
| } |
| %4:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Switch) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %3 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %int_0 = OpConstant %int 0 |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %main = OpFunction %void None %3 |
| %4 = OpLabel |
| OpSelectionMerge %13 None |
| OpSwitch %int_0 %10 0 %11 1 %12 |
| %10 = OpLabel |
| OpBranch %13 |
| %11 = OpLabel |
| OpBranch %13 |
| %12 = OpLabel |
| OpBranch %13 |
| %13 = OpLabel |
| %14 = OpPhi %int %int_0 %10 %int_1 %11 %int_2 %12 |
| %15 = OpIAdd %int %14 %14 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = switch 0i [c: (default, $B2), c: (0i, $B3), c: (1i, $B4)] { # switch_1 |
| $B2: { # case |
| exit_switch 0i # switch_1 |
| } |
| $B3: { # case |
| exit_switch 1i # switch_1 |
| } |
| $B4: { # case |
| exit_switch 2i # switch_1 |
| } |
| } |
| %3:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Switch_FromIfBreak) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %3 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %bool = OpTypeBool |
| %int_0 = OpConstant %int 0 |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %int_3 = OpConstant %int 3 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %3 |
| %4 = OpLabel |
| OpSelectionMerge %13 None |
| OpSwitch %int_0 %10 0 %11 1 %12 |
| %10 = OpLabel |
| OpBranch %13 |
| %11 = OpLabel |
| OpBranch %13 |
| %12 = OpLabel |
| OpSelectionMerge %20 None |
| OpBranchConditional %true %20 %21 |
| %21 = OpLabel |
| OpBranch %13 |
| %20 = OpLabel |
| OpBranch %13 |
| %13 = OpLabel |
| %14 = OpPhi %int %int_0 %10 %int_1 %11 %int_2 %20 %int_3 %21 |
| %15 = OpIAdd %int %14 %14 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = switch 0i [c: (default, $B2), c: (0i, $B3), c: (1i, $B4)] { # switch_1 |
| $B2: { # case |
| exit_switch 0i # switch_1 |
| } |
| $B3: { # case |
| exit_switch 1i # switch_1 |
| } |
| $B4: { # case |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_if # if_1 |
| } |
| $B6: { # false |
| exit_switch 3i # switch_1 |
| } |
| } |
| exit_switch 2i # switch_1 |
| } |
| } |
| %3:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Switch_FromIfBreak_InDefault) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %9 = OpTypeFunction %void |
| %float = OpTypeFloat 32 |
| %bool = OpTypeBool |
| %float_7 = OpConstant %float 7 |
| %float_8 = OpConstant %float 8 |
| %uint = OpTypeInt 32 0 |
| %uint_0 = OpConstant %uint 0 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %9 |
| %45 = OpLabel |
| OpSelectionMerge %52 None |
| OpSwitch %uint_0 %53 |
| %53 = OpLabel |
| OpBranch %54 |
| %54 = OpLabel |
| OpSelectionMerge %84 None |
| OpBranchConditional %true %52 %84 |
| %84 = OpLabel |
| OpBranch %52 |
| %52 = OpLabel |
| %85 = OpPhi %float %float_7 %54 %float_8 %84 |
| %100 = OpFAdd %float %85 %85 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = switch 0u [c: (default, $B2)] { # switch_1 |
| $B2: { # case |
| if true [t: $B3, f: $B4] { # if_1 |
| $B3: { # true |
| exit_switch 7.0f # switch_1 |
| } |
| $B4: { # false |
| exit_if # if_1 |
| } |
| } |
| exit_switch 8.0f # switch_1 |
| } |
| } |
| %3:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Switch_FromIf_BothJumpToMerge_InDefault) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %9 = OpTypeFunction %void |
| %float = OpTypeFloat 32 |
| %bool = OpTypeBool |
| %float_7 = OpConstant %float 7 |
| %float_8 = OpConstant %float 8 |
| %uint = OpTypeInt 32 0 |
| %uint_0 = OpConstant %uint 0 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %9 |
| %45 = OpLabel |
| OpSelectionMerge %52 None |
| OpSwitch %uint_0 %53 |
| %53 = OpLabel |
| OpBranch %54 |
| %54 = OpLabel |
| OpSelectionMerge %84 None |
| OpBranchConditional %true %52 %52 |
| %84 = OpLabel |
| OpBranch %52 |
| %52 = OpLabel |
| %85 = OpPhi %float %float_7 %54 %float_8 %84 |
| %100 = OpFAdd %float %85 %85 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = switch 0u [c: (default, $B2)] { # switch_1 |
| $B2: { # case |
| %3:bool = or true, true |
| if %3 [t: $B3, f: $B4] { # if_1 |
| $B3: { # true |
| exit_switch 7.0f # switch_1 |
| } |
| $B4: { # false |
| unreachable |
| } |
| } |
| exit_switch 8.0f # switch_1 |
| } |
| } |
| %4:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Switch_FromIfBreakBoth_InDefault) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %9 = OpTypeFunction %void |
| %float = OpTypeFloat 32 |
| %bool = OpTypeBool |
| %float_7 = OpConstant %float 7 |
| %float_8 = OpConstant %float 8 |
| %float_9 = OpConstant %float 9 |
| %uint = OpTypeInt 32 0 |
| %uint_0 = OpConstant %uint 0 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %9 |
| %45 = OpLabel |
| OpSelectionMerge %52 None |
| OpSwitch %uint_0 %53 |
| %53 = OpLabel |
| OpBranch %54 |
| %54 = OpLabel |
| OpSelectionMerge %84 None |
| OpBranchConditional %true %55 %56 |
| %55 = OpLabel |
| OpBranch %52 |
| %56 = OpLabel |
| OpBranch %52 |
| %84 = OpLabel |
| OpBranch %52 |
| %52 = OpLabel |
| %85 = OpPhi %float %float_7 %55 %float_8 %56 %float_9 %84 |
| %100 = OpFAdd %float %85 %85 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = switch 0u [c: (default, $B2)] { # switch_1 |
| $B2: { # case |
| if true [t: $B3, f: $B4] { # if_1 |
| $B3: { # true |
| exit_switch 7.0f # switch_1 |
| } |
| $B4: { # false |
| exit_switch 8.0f # switch_1 |
| } |
| } |
| exit_switch 9.0f # switch_1 |
| } |
| } |
| %3:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_FromIfBreak) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %9 = OpTypeFunction %void |
| %float = OpTypeFloat 32 |
| %bool = OpTypeBool |
| %float_7 = OpConstant %float 7 |
| %float_8 = OpConstant %float 8 |
| %uint = OpTypeInt 32 0 |
| %uint_0 = OpConstant %uint 0 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %9 |
| %45 = OpLabel |
| OpBranch %50 |
| %50 = OpLabel |
| OpLoopMerge %52 %55 None |
| OpBranch %60 |
| %55 = OpLabel |
| OpBranch %50 |
| %60 = OpLabel |
| OpSelectionMerge %84 None |
| OpBranchConditional %true %52 %84 |
| %84 = OpLabel |
| OpBranch %52 |
| %52 = OpLabel |
| %85 = OpPhi %float %float_7 %60 %float_8 %84 |
| %100 = OpFAdd %float %85 %85 |
| OpReturn |
| OpFunctionEnd |
| |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| if true [t: $B4, f: $B5] { # if_1 |
| $B4: { # true |
| exit_loop 7.0f # loop_1 |
| } |
| $B5: { # false |
| exit_if # if_1 |
| } |
| } |
| exit_loop 8.0f # loop_1 |
| } |
| $B3: { # continuing |
| next_iteration # -> $B2 |
| } |
| } |
| %3:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_ContinueIsHeader) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %false %20 |
| OpLoopMerge %99 %20 None |
| OpBranchConditional %101 %99 %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%2:bool): { # body |
| if %2 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration false # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_WithContinue) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %false %30 |
| OpLoopMerge %99 %30 None |
| OpBranchConditional %101 %99 %25 |
| %25 = OpLabel |
| OpBranch %30 |
| %30 = OpLabel |
| %102 = OpCopyObject %bool %101 |
| OpBranch %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%2:bool): { # body |
| if %2 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| %3:bool = let %2 |
| next_iteration false # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_WithContinue_PhiInContinue) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| OpLoopMerge %99 %30 None |
| OpBranchConditional %true %24 %30 |
| %24 = OpLabel |
| OpBranch %30 |
| %30 = OpLabel |
| %101 = OpPhi %bool %true %24 %false %20 |
| %102 = OpCopyObject %bool %101 |
| OpBranchConditional %true %99 %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| if true [t: $B4, f: $B5] { # if_1 |
| $B4: { # true |
| continue true # -> $B3 |
| } |
| $B5: { # false |
| continue false # -> $B3 |
| } |
| } |
| unreachable |
| } |
| $B3 (%2:bool): { # continuing |
| %3:bool = let %2 |
| break_if true # -> [t: exit_loop loop_1, f: $B2] |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_WithMultiblockContinue) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %false %31 |
| OpLoopMerge %99 %30 None |
| OpBranchConditional %101 %99 %25 |
| %25 = OpLabel |
| OpBranch %30 |
| %30 = OpLabel |
| %102 = OpCopyObject %bool %101 |
| OpBranch %31 |
| %31 = OpLabel |
| OpBranch %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%2:bool): { # body |
| if %2 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| %3:bool = let %2 |
| next_iteration false # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Loop_BranchConditionalBreak) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %int = OpTypeInt 32 1 |
| %true = OpConstantTrue %bool |
| %int_1 = OpConstant %int 1 |
| %int_2 = OpConstant %int 2 |
| %int_3 = OpConstant %int 3 |
| %int_4 = OpConstant %int 4 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %1 |
| %1 = OpLabel |
| OpLoopMerge %99 %20 None |
| OpBranchConditional %true %50 %20 |
| %50 = OpLabel |
| %6 = OpIAdd %int %int_1 %int_2 |
| OpBranch %99 |
| %20 = OpLabel |
| OpBranch %1 |
| %99 = OpLabel |
| %8 = OpPhi %int %6 %50 |
| %9 = OpIAdd %int %8 %8 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| if true [t: $B4, f: $B5] { # if_1 |
| $B4: { # true |
| %3:i32 = spirv.add<i32> 1i, 2i |
| exit_loop %3 # loop_1 |
| } |
| $B5: { # false |
| continue # -> $B3 |
| } |
| } |
| unreachable |
| } |
| $B3: { # continuing |
| next_iteration # -> $B2 |
| } |
| } |
| %4:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // Phis must act as if they are simultaneously assigned. %101 and %102 should exchange values on |
| // each iteration, and never have the same value. |
| TEST_F(SpirvParserTest, Phi_SimultaneousAssignment) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| OpName %101 "default_true" |
| OpName %102 "default_false" |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %102 %20 |
| %102 = OpPhi %bool %false %10 %101 %20 |
| OpLoopMerge %99 %20 None |
| OpBranchConditional %true %99 %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true, false # -> $B3 |
| } |
| $B3 (%2:bool, %3:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration %3, %2 # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_SingleBlockLoopIndex) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| OpName %2 "computed" |
| OpName %3 "copied" |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %uint = OpTypeInt 32 0 |
| %pty = OpTypePointer Private %uint |
| %1 = OpVariable %pty Private |
| %boolpty = OpTypePointer Private %bool |
| %7 = OpVariable %boolpty Private |
| %8 = OpVariable %boolpty Private |
| %uint_0 = OpConstant %uint 0 |
| %uint_1 = OpConstant %uint 1 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %main_start = OpLabel |
| OpBranch %10 |
| ; Use an outer loop to show we put the new variable in the |
| ; smallest enclosing scope. |
| %10 = OpLabel |
| %101 = OpLoad %bool %7 |
| %102 = OpLoad %bool %8 |
| %103 = OpIAdd %uint %uint_0 %uint_0 |
| OpLoopMerge %99 %89 None |
| OpBranchConditional %101 %99 %20 |
| %20 = OpLabel |
| %2 = OpPhi %uint %103 %10 %4 %20 ; gets computed value |
| %3 = OpPhi %uint %uint_1 %10 %3 %20 ; gets itself |
| %4 = OpIAdd %uint %2 %uint_1 |
| OpLoopMerge %79 %20 None |
| OpBranchConditional %102 %79 %20 |
| %79 = OpLabel |
| OpBranch %89 |
| %89 = OpLabel |
| OpBranch %10 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| $B1: { # root |
| %1:ptr<private, u32, read_write> = var undef |
| %2:ptr<private, bool, read_write> = var undef |
| %3:ptr<private, bool, read_write> = var undef |
| } |
| |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B2: { |
| loop [b: $B3, c: $B4] { # loop_1 |
| $B3: { # body |
| %5:bool = load %2 |
| %6:bool = load %3 |
| %7:u32 = spirv.add<u32> 0u, 0u |
| if %5 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| loop [i: $B7, b: $B8, c: $B9] { # loop_2 |
| $B7: { # initializer |
| next_iteration %7, 1u # -> $B8 |
| } |
| $B8 (%8:u32, %9:u32): { # body |
| %10:u32 = spirv.add<u32> %8, 1u |
| if %6 [t: $B10, f: $B11] { # if_2 |
| $B10: { # true |
| exit_loop # loop_2 |
| } |
| $B11: { # false |
| continue # -> $B9 |
| } |
| } |
| unreachable |
| } |
| $B9: { # continuing |
| next_iteration %10, %9 # -> $B8 |
| } |
| } |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_MultiBlockLoopIndex) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %uint = OpTypeInt 32 0 |
| %pty = OpTypePointer Private %uint |
| %1 = OpVariable %pty Private |
| %boolpty = OpTypePointer Private %bool |
| %7 = OpVariable %boolpty Private |
| %8 = OpVariable %boolpty Private |
| %uint_0 = OpConstant %uint 0 |
| %uint_1 = OpConstant %uint 1 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %5 = OpLabel |
| OpBranch %10 |
| ; Use an outer loop to show we put the new variable in the |
| ; smallest enclosing scope. |
| %10 = OpLabel |
| %101 = OpLoad %bool %7 |
| %102 = OpLoad %bool %8 |
| OpLoopMerge %99 %89 None |
| OpBranchConditional %101 %99 %20 |
| %20 = OpLabel |
| %2 = OpPhi %uint %uint_0 %10 %4 %30 ; gets computed value |
| %3 = OpPhi %uint %uint_1 %10 %3 %30 ; gets itself |
| OpLoopMerge %79 %30 None |
| OpBranchConditional %102 %79 %30 |
| %30 = OpLabel ; continue target for inner loop |
| %4 = OpIAdd %uint %2 %uint_1 |
| OpBranch %20 |
| %79 = OpLabel ; merge for inner loop |
| OpBranch %89 |
| %89 = OpLabel ; continue target for outer loop |
| OpBranch %10 |
| %99 = OpLabel ; merge for outer loop |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| $B1: { # root |
| %1:ptr<private, u32, read_write> = var undef |
| %2:ptr<private, bool, read_write> = var undef |
| %3:ptr<private, bool, read_write> = var undef |
| } |
| |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B2: { |
| loop [b: $B3, c: $B4] { # loop_1 |
| $B3: { # body |
| %5:bool = load %2 |
| %6:bool = load %3 |
| if %5 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| loop [i: $B7, b: $B8, c: $B9] { # loop_2 |
| $B7: { # initializer |
| next_iteration 0u, 1u # -> $B8 |
| } |
| $B8 (%7:u32, %8:u32): { # body |
| if %6 [t: $B10, f: $B11] { # if_2 |
| $B10: { # true |
| exit_loop # loop_2 |
| } |
| $B11: { # false |
| continue # -> $B9 |
| } |
| } |
| unreachable |
| } |
| $B9: { # continuing |
| %9:u32 = spirv.add<u32> %7, 1u |
| next_iteration %9, %8 # -> $B8 |
| } |
| } |
| continue # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_ValueFromLoopBodyAndContinuing) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %uint = OpTypeInt 32 0 |
| %pty = OpTypePointer Private %uint |
| %1 = OpVariable %pty Private |
| %boolpty = OpTypePointer Private %bool |
| %17 = OpVariable %boolpty Private |
| %uint_0 = OpConstant %uint 0 |
| %uint_1 = OpConstant %uint 1 |
| %true = OpConstantTrue %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %9 = OpLabel |
| %101 = OpLoad %bool %17 |
| OpBranch %10 |
| ; Use an outer loop to show we put the new variable in the |
| ; smallest enclosing scope. |
| %10 = OpLabel |
| OpLoopMerge %99 %89 None |
| OpBranch %20 |
| %20 = OpLabel |
| %2 = OpPhi %uint %uint_0 %10 %4 %30 ; gets computed value |
| %5 = OpPhi %uint %uint_1 %10 %7 %30 |
| %4 = OpIAdd %uint %2 %uint_1 ; define %4 |
| %6 = OpIAdd %uint %4 %uint_1 ; use %4 |
| OpLoopMerge %79 %30 None |
| OpBranchConditional %101 %79 %30 |
| %30 = OpLabel |
| %7 = OpIAdd %uint %4 %6 ; use %4 again |
| %8 = OpCopyObject %uint %5 ; use %5 |
| OpBranchConditional %true %20 %79 |
| %79 = OpLabel |
| OpBranch %89 |
| %89 = OpLabel |
| OpBranchConditional %true %10 %99 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| $B1: { # root |
| %1:ptr<private, u32, read_write> = var undef |
| %2:ptr<private, bool, read_write> = var undef |
| } |
| |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B2: { |
| %4:bool = load %2 |
| loop [b: $B3, c: $B4] { # loop_1 |
| $B3: { # body |
| loop [i: $B5, b: $B6, c: $B7] { # loop_2 |
| $B5: { # initializer |
| next_iteration 0u, 1u # -> $B6 |
| } |
| $B6 (%5:u32, %6:u32): { # body |
| %7:u32 = spirv.add<u32> %5, 1u |
| %8:u32 = spirv.add<u32> %7, 1u |
| if %4 [t: $B8, f: $B9] { # if_1 |
| $B8: { # true |
| exit_loop # loop_2 |
| } |
| $B9: { # false |
| continue # -> $B7 |
| } |
| } |
| unreachable |
| } |
| $B7: { # continuing |
| %9:u32 = spirv.add<u32> %7, %8 |
| %10:u32 = let %6 |
| %11:bool = not true |
| break_if %11 next_iteration: [ %7, %9 ] # -> [t: exit_loop loop_2, f: $B6] |
| } |
| } |
| continue # -> $B4 |
| } |
| $B4: { # continuing |
| %12:bool = not true |
| break_if %12 # -> [t: exit_loop loop_1, f: $B3] |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_FromElseAndThen) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %uint = OpTypeInt 32 0 |
| %pty = OpTypePointer Private %uint |
| %1 = OpVariable %pty Private |
| %boolpty = OpTypePointer Private %bool |
| %7 = OpVariable %boolpty Private |
| %8 = OpVariable %boolpty Private |
| %uint_0 = OpConstant %uint 0 |
| %uint_1 = OpConstant %uint 1 |
| %uint_3 = OpConstant %uint 3 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %5 = OpLabel |
| %101 = OpLoad %bool %7 |
| %102 = OpLoad %bool %8 |
| OpBranch %10 |
| ; Use an outer loop to show we put the new variable in the |
| ; smallest enclosing scope. |
| %10 = OpLabel |
| OpLoopMerge %99 %89 None |
| OpBranchConditional %101 %99 %20 |
| %20 = OpLabel ; if seleciton |
| OpSelectionMerge %79 None |
| OpBranchConditional %102 %30 %40 |
| %30 = OpLabel |
| OpBranch %89 |
| %40 = OpLabel |
| OpBranch %89 |
| %79 = OpLabel ; disconnected selection merge node |
| OpBranch %89 |
| %89 = OpLabel |
| %2 = OpPhi %uint %uint_0 %30 %uint_1 %40 %uint_3 %79 |
| OpStore %1 %2 |
| OpBranch %10 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| $B1: { # root |
| %1:ptr<private, u32, read_write> = var undef |
| %2:ptr<private, bool, read_write> = var undef |
| %3:ptr<private, bool, read_write> = var undef |
| } |
| |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B2: { |
| %5:bool = load %2 |
| %6:bool = load %3 |
| loop [b: $B3, c: $B4] { # loop_1 |
| $B3: { # body |
| if %5 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| if %6 [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| continue 0u # -> $B4 |
| } |
| $B8: { # false |
| continue 1u # -> $B4 |
| } |
| } |
| continue 3u # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4 (%7:u32): { # continuing |
| store %1, %7 |
| next_iteration # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_FromHeaderAndThen) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %uint = OpTypeInt 32 0 |
| %pty = OpTypePointer Private %uint |
| %1 = OpVariable %pty Private |
| %boolpty = OpTypePointer Private %bool |
| %7 = OpVariable %boolpty Private |
| %8 = OpVariable %boolpty Private |
| %uint_0 = OpConstant %uint 0 |
| %uint_1 = OpConstant %uint 1 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %5 = OpLabel |
| %101 = OpLoad %bool %7 |
| %102 = OpLoad %bool %8 |
| OpBranch %10 |
| ; Use an outer loop to show we put the new variable in the |
| ; smallest enclosing scope. |
| %10 = OpLabel |
| OpLoopMerge %99 %89 None |
| OpBranchConditional %101 %99 %20 |
| %20 = OpLabel ; if seleciton |
| OpSelectionMerge %79 None |
| OpBranchConditional %102 %30 %89 |
| %30 = OpLabel |
| OpBranch %89 |
| %79 = OpLabel ; disconnected selection merge node |
| OpUnreachable |
| %89 = OpLabel |
| %2 = OpPhi %uint %uint_0 %20 %uint_1 %30 |
| %3 = OpPhi %uint %uint_1 %20 %uint_0 %30 |
| %4 = OpIAdd %uint %2 %3 |
| OpStore %1 %4 |
| OpBranch %10 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| $B1: { # root |
| %1:ptr<private, u32, read_write> = var undef |
| %2:ptr<private, bool, read_write> = var undef |
| %3:ptr<private, bool, read_write> = var undef |
| } |
| |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B2: { |
| %5:bool = load %2 |
| %6:bool = load %3 |
| loop [b: $B3, c: $B4] { # loop_1 |
| $B3: { # body |
| if %5 [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| if %6 [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| continue 1u, 0u # -> $B4 |
| } |
| $B8: { # false |
| continue 0u, 1u # -> $B4 |
| } |
| } |
| unreachable |
| } |
| } |
| unreachable |
| } |
| $B4 (%7:u32, %8:u32): { # continuing |
| %9:u32 = spirv.add<u32> %7, %8 |
| store %1, %9 |
| next_iteration # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // If the only use of a combinatorially computed ID is as the value in an OpPhi, then we still have |
| // to emit it. The algorithm fix is to always count uses in Phis. This is the reduced case from the |
| // bug report. |
| // |
| // * The only use of %12 is in the phi. |
| // * The only use of %11 is in %12. |
| // * Both definitions need to be emitted to the output. |
| // |
| // https://crbug.com/215 |
| TEST_F(SpirvParserTest, Phi_UseInPhiCountsAsUse) { |
| EXPECT_IR( |
| R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| %11 = OpLogicalAnd %bool %true %true |
| %12 = OpLogicalNot %bool %11 ; |
| OpSelectionMerge %99 None |
| OpBranchConditional %true %20 %99 |
| %20 = OpLabel |
| OpBranch %99 |
| %99 = OpLabel |
| %101 = OpPhi %bool %11 %10 %12 %20 |
| %102 = OpCopyObject %bool %101 ;; ensure a use of %101 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:bool = and true, true |
| %3:bool = not %2 |
| %4:bool = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| exit_if %3 # if_1 |
| } |
| $B3: { # false |
| exit_if %2 # if_1 |
| } |
| } |
| %5:bool = let %4 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // Value %999 is defined deep in control flow, then we arrange for it to dominate the backedge of |
| // the outer loop. The %999 value is then fed back into the phi in the loop header. So %999 needs |
| // to be hoisted out of the loop. The phi assignment needs to use the hoisted variable. The hoisted |
| // variable needs to be placed such that its scope encloses that phi in the header of the outer |
| // loop. The compiler needs to "see" that there is an implicit use of %999 in the backedge block of |
| // that outer loop. |
| // |
| // https://crbug.com/1649 |
| TEST_F(SpirvParserTest, Phi_PhiInLoopHeader_FedByHoistedVar_PhiUnused) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %999 %80 |
| OpLoopMerge %99 %80 None |
| OpBranchConditional %true %30 %99 |
| %30 = OpLabel |
| OpSelectionMerge %50 None |
| OpBranchConditional %true %40 %50 |
| %40 = OpLabel |
| %999 = OpCopyObject %bool %true |
| OpBranch %60 |
| %50 = OpLabel |
| OpReturn |
| %60 = OpLabel ; if merge |
| OpBranch %80 |
| %80 = OpLabel ; continue target |
| OpBranch %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%2:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| %3:bool = let true |
| continue %3 # -> $B4 |
| } |
| $B8: { # false |
| exit_if # if_2 |
| } |
| } |
| ret |
| } |
| $B6: { # false |
| exit_loop # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4 (%4:bool): { # continuing |
| next_iteration %4 # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // Value %999 is defined deep in control flow, then we arrange for it to dominate the backedge of |
| // the outer loop. The %999 value is then fed back into the phi in the loop header. So %999 needs |
| // to be hoisted out of the loop. The phi assignment needs to use the hoisted variable. The hoisted |
| // variable needs to be placed such that its scope encloses that phi in the header of the outer |
| // loop. The compiler needs to "see" that there is an implicit use of %999 in the backedge block of |
| // that outer loop. |
| // |
| // https://crbug.com/1649 |
| TEST_F(SpirvParserTest, Phi_PhiInLoopHeader_FedByHoistedVar_PhiUsed) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %999 %80 |
| OpLoopMerge %99 %80 None |
| OpBranchConditional %true %30 %99 |
| %30 = OpLabel |
| OpSelectionMerge %50 None |
| OpBranchConditional %true %40 %50 |
| %40 = OpLabel |
| %999 = OpCopyObject %bool %true |
| OpBranch %60 |
| %50 = OpLabel |
| OpReturn |
| %60 = OpLabel ; if merge |
| OpBranch %80 |
| %80 = OpLabel ; continue target |
| OpBranch %20 |
| %99 = OpLabel |
| %1000 = OpCopyObject %bool %101 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:bool = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%3:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| %4:bool = let true |
| continue %4 # -> $B4 |
| } |
| $B8: { # false |
| exit_if # if_2 |
| } |
| } |
| ret |
| } |
| $B6: { # false |
| exit_loop %3 # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4 (%5:bool): { # continuing |
| next_iteration %5 # -> $B3 |
| } |
| } |
| %6:bool = let %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // This is a reduction of one of the hard parts of test case |
| // vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm |
| // In particular, see the data flow around %114 in that case. |
| // |
| // Here value %999 is is a *phi* defined deep in control flow, then we arrange for it to dominate |
| // the backedge of the outer loop. The %999 value is then fed back into the phi in the loop header. |
| // The variable generated to hold the %999 value needs to be placed such that its scope encloses |
| // that phi in the header of the outer loop. The compiler needs to "see" that there is an implicit |
| // use of %999 in the backedge block of that outer loop. |
| // |
| // https://crbug.com/1649 |
| TEST_F(SpirvParserTest, Phi_PhiInLoopHeader_FedByPhi_PhiUnused) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %999 %80 |
| OpLoopMerge %99 %80 None |
| OpBranchConditional %true %99 %30 |
| %30 = OpLabel |
| OpLoopMerge %70 %60 None |
| OpBranch %40 |
| %40 = OpLabel |
| OpBranchConditional %true %60 %50 |
| %50 = OpLabel |
| OpBranch %60 |
| %60 = OpLabel ; inner continue |
| %999 = OpPhi %bool %true %40 %false %50 |
| OpBranchConditional %true %70 %30 |
| %70 = OpLabel ; inner merge |
| OpBranch %80 |
| %80 = OpLabel ; outer continue target |
| OpBranch %20 |
| %99 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%2:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop # loop_1 |
| } |
| $B6: { # false |
| %3:bool = loop [b: $B7, c: $B8] { # loop_2 |
| $B7: { # body |
| if true [t: $B9, f: $B10] { # if_2 |
| $B9: { # true |
| continue true # -> $B8 |
| } |
| $B10: { # false |
| continue false # -> $B8 |
| } |
| } |
| unreachable |
| } |
| $B8 (%4:bool): { # continuing |
| break_if true exit_loop: [ %4 ] # -> [t: exit_loop loop_2, f: $B7] |
| } |
| } |
| continue %3 # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4 (%5:bool): { # continuing |
| next_iteration %5 # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // This is a reduction of one of the hard parts of test case |
| // vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm |
| // In particular, see the data flow around %114 in that case. |
| // |
| // Here value %999 is is a *phi* defined deep in control flow, then we arrange for it to dominate |
| // the backedge of the outer loop. The %999 value is then fed back into the phi in the loop header. |
| // The variable generated to hold the %999 value needs to be placed such that its scope encloses |
| // that phi in the header of the outer loop. The compiler needs to "see" that there is an implicit |
| // use of %999 in the backedge block of that outer loop. |
| // |
| // https://crbug.com/1649 |
| TEST_F(SpirvParserTest, Phi_PhiInLoopHeader_FedByPhi_PhiUsed) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %false = OpConstantFalse %bool |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %20 |
| %20 = OpLabel |
| %101 = OpPhi %bool %true %10 %999 %80 |
| OpLoopMerge %99 %80 None |
| OpBranchConditional %true %99 %30 |
| %30 = OpLabel |
| OpLoopMerge %70 %60 None |
| OpBranch %40 |
| %40 = OpLabel |
| OpBranchConditional %true %60 %50 |
| %50 = OpLabel |
| OpBranch %60 |
| %60 = OpLabel ; inner continue |
| %999 = OpPhi %bool %true %40 %false %50 |
| OpBranchConditional %true %70 %30 |
| %70 = OpLabel ; inner merge |
| OpBranch %80 |
| %80 = OpLabel ; outer continue target |
| OpBranch %20 |
| %99 = OpLabel |
| %1000 = OpCopyObject %bool %101 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:bool = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%3:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_loop %3 # loop_1 |
| } |
| $B6: { # false |
| %4:bool = loop [b: $B7, c: $B8] { # loop_2 |
| $B7: { # body |
| if true [t: $B9, f: $B10] { # if_2 |
| $B9: { # true |
| continue true # -> $B8 |
| } |
| $B10: { # false |
| continue false # -> $B8 |
| } |
| } |
| unreachable |
| } |
| $B8 (%5:bool): { # continuing |
| break_if true exit_loop: [ %5 ] # -> [t: exit_loop loop_2, f: $B7] |
| } |
| } |
| continue %4 # -> $B4 |
| } |
| } |
| unreachable |
| } |
| $B4 (%6:bool): { # continuing |
| next_iteration %6 # -> $B3 |
| } |
| } |
| %7:bool = let %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // A phi in an unreachable block may have no operands. |
| TEST_F(SpirvParserTest, Phi_UnreachableLoopMerge) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %uint = OpTypeInt 32 0 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %10 = OpLabel |
| OpBranch %99 |
| %99 = OpLabel |
| OpLoopMerge %101 %99 None |
| OpBranch %99 |
| %101 = OpLabel |
| %102 = OpPhi %uint |
| OpUnreachable |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| continue # -> $B3 |
| } |
| $B3: { # continuing |
| next_iteration # -> $B2 |
| } |
| } |
| unreachable |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_SwitchDefaultIsMerge) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| %void = OpTypeVoid |
| %int = OpTypeInt 32 1 |
| %int_1 = OpConstant %int 1 |
| %ep_type = OpTypeFunction %void |
| %main = OpFunction %void None %ep_type |
| %67 = OpLabel |
| %69 = OpIAdd %int %int_1 %int_1 |
| OpSelectionMerge %70 None |
| OpSwitch %int_1 %70 0 %71 |
| %71 = OpLabel |
| %82 = OpIAdd %int %69 %int_1 |
| OpBranch %70 |
| %70 = OpLabel |
| %64 = OpPhi %int %69 %67 %82 %71 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| %2:i32 = spirv.add<i32> 1i, 1i |
| %3:i32 = switch 1i [c: (default, $B2), c: (0i, $B3)] { # switch_1 |
| $B2: { # case |
| exit_switch %2 # switch_1 |
| } |
| $B3: { # case |
| %4:i32 = spirv.add<i32> %2, 1i |
| exit_switch %4 # switch_1 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_LoopWithIf) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %11 = OpTypeFunction %void |
| %float = OpTypeFloat 32 |
| %bool = OpTypeBool |
| %float_0 = OpConstant %float 0 |
| %float_1 = OpConstant %float 1 |
| %float_2 = OpConstant %float 2 |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %11 |
| %33 = OpLabel |
| OpBranch %40 |
| %40 = OpLabel |
| OpLoopMerge %47 %43 None |
| OpBranchConditional %true %48 %47 |
| %48 = OpLabel |
| OpBranch %49 |
| %49 = OpLabel |
| OpSelectionMerge %59 None |
| OpBranchConditional %true %47 %59 |
| %59 = OpLabel |
| OpBranch %43 |
| %43 = OpLabel |
| OpBranchConditional %true %47 %40 |
| %47 = OpLabel |
| %60 = OpPhi %float %float_0 %40 %float_1 %49 %float_2 %43 |
| %61 = OpFAdd %float %60 %60 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| if true [t: $B4, f: $B5] { # if_1 |
| $B4: { # true |
| if true [t: $B6, f: $B7] { # if_2 |
| $B6: { # true |
| exit_loop 1.0f # loop_1 |
| } |
| $B7: { # false |
| exit_if # if_2 |
| } |
| } |
| continue # -> $B3 |
| } |
| $B5: { # false |
| exit_loop 0.0f # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B3: { # continuing |
| break_if true exit_loop: [ 2.0f ] # -> [t: exit_loop loop_1, f: $B2] |
| } |
| } |
| %3:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_InLoopBody) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %7 = OpTypeFunction %void |
| %bool = OpTypeBool |
| %float = OpTypeFloat 32 |
| %float_1 = OpConstant %float 1 |
| %float_0 = OpConstant %float 0 |
| %main = OpFunction %void None %7 |
| %29 = OpLabel |
| OpBranch %31 |
| %31 = OpLabel |
| OpLoopMerge %35 %34 None |
| OpBranch %36 |
| %36 = OpLabel |
| %33 = OpPhi %float %float_1 %31 %float_0 %37 |
| %99 = OpFAdd %float %33 %33 |
| OpReturn |
| %37 = OpLabel |
| OpBranch %36 |
| %34 = OpLabel |
| OpBranch %31 |
| %35 = OpLabel |
| OpUnreachable |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| loop [b: $B2, c: $B3] { # loop_1 |
| $B2: { # body |
| %2:f32 = add 1.0f, 1.0f |
| ret |
| } |
| $B3: { # continuing |
| next_iteration # -> $B2 |
| } |
| } |
| unreachable |
| } |
| } |
| )"); |
| } |
| |
| // This is testing that an `OpPhi` value defined inside a loop (like %23) is correctly propagated |
| // when accessed from an `OpPhi` outside of the look (like %45). These phi values should turn into |
| // results on the loop, and then accessed as such in the two adds (%48 and %49) |
| TEST_F(SpirvParserTest, Phi_Propagated) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %80 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %1 = OpConstant %int 1 |
| %2 = OpConstant %int 2 |
| %3 = OpConstant %int 3 |
| %4 = OpConstant %int 4 |
| %5 = OpConstant %int 5 |
| %bool = OpTypeBool |
| %8 = OpConstantTrue %bool |
| %main = OpFunction %void None %80 |
| %21 = OpLabel |
| OpBranch %22 |
| |
| ; 1st loop |
| %22 = OpLabel |
| %23 = OpPhi %int %1 %21 %2 %25 |
| %24 = OpIAdd %int %23 %23 |
| OpLoopMerge %30 %25 None |
| OpBranchConditional %8 %25 %30 |
| %25 = OpLabel |
| OpBranch %22 |
| |
| ; loop merge && if start |
| %30 = OpLabel |
| %99 = OpCopyObject %int %2 |
| OpSelectionMerge %34 None |
| OpBranchConditional %8 %35 %34 |
| %35 = OpLabel |
| OpBranch %36 |
| |
| ; 2nd loop |
| %36 = OpLabel |
| %37 = OpPhi %int %3 %35 %4 %39 |
| %40 = OpPhi %int %23 %35 %5 %39 |
| %41 = OpIAdd %int %37 %40 |
| OpLoopMerge %43 %39 None |
| OpBranchConditional %8 %39 %43 |
| %39 = OpLabel |
| OpBranch %36 |
| %43 = OpLabel |
| OpBranch %34 |
| |
| ; if merge |
| %34 = OpLabel |
| %45 = OpPhi %int %99 %30 %37 %43 |
| %46 = OpPhi %int %3 %30 %40 %43 |
| %47 = OpPhi %int %4 %30 %40 %43 |
| %48 = OpIAdd %int %45 %46 |
| %49 = OpIAdd %int %48 %47 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:i32 = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration 1i # -> $B3 |
| } |
| $B3 (%3:i32): { # body |
| %4:i32 = spirv.add<i32> %3, %3 |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| continue # -> $B4 |
| } |
| $B6: { # false |
| exit_loop %3 # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration 2i # -> $B3 |
| } |
| } |
| %5:i32 = let 2i |
| %6:i32, %7:i32, %8:i32 = if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| %9:i32, %10:i32 = loop [i: $B9, b: $B10, c: $B11] { # loop_2 |
| $B9: { # initializer |
| next_iteration 3i, %2 # -> $B10 |
| } |
| $B10 (%11:i32, %12:i32): { # body |
| %13:i32 = spirv.add<i32> %11, %12 |
| if true [t: $B12, f: $B13] { # if_3 |
| $B12: { # true |
| continue # -> $B11 |
| } |
| $B13: { # false |
| exit_loop %11, %12 # loop_2 |
| } |
| } |
| unreachable |
| } |
| $B11: { # continuing |
| next_iteration 4i, 5i # -> $B10 |
| } |
| } |
| exit_if %9, %10, %10 # if_2 |
| } |
| $B8: { # false |
| exit_if %5, 3i, 4i # if_2 |
| } |
| } |
| %14:i32 = spirv.add<i32> %6, %7 |
| %15:i32 = spirv.add<i32> %14, %8 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Propagated_PropagatedPhiValue) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %8 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %1 = OpConstant %int 1 |
| %2 = OpConstant %int 2 |
| %3 = OpConstant %int 3 |
| %4 = OpConstant %int 4 |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %8 |
| %22 = OpLabel |
| OpBranch %27 |
| %27 = OpLabel |
| %28 = OpPhi %int %2 %22 %3 %30 |
| %31 = OpPhi %int %1 %22 %4 %30 |
| OpLoopMerge %35 %30 None |
| OpBranchConditional %true %36 %35 |
| %36 = OpLabel |
| %29 = OpCopyObject %int %1 |
| OpSelectionMerge %40 None |
| OpBranchConditional %true %41 %40 |
| %41 = OpLabel |
| OpBranch %35 |
| %40 = OpLabel |
| OpBranch %30 |
| %30 = OpLabel |
| OpBranch %27 |
| %35 = OpLabel |
| %42 = OpPhi %int %28 %27 %29 %41 |
| %43 = OpIAdd %int %42 %42 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:i32 = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration 2i, 1i # -> $B3 |
| } |
| $B3 (%3:i32, %4:i32): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| %5:i32 = let 1i |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| exit_loop %5 # loop_1 |
| } |
| $B8: { # false |
| exit_if # if_2 |
| } |
| } |
| continue # -> $B4 |
| } |
| $B6: { # false |
| exit_loop %3 # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4: { # continuing |
| next_iteration 3i, 4i # -> $B3 |
| } |
| } |
| %6:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // The object in %29 is propagated up through the IF as a result and then is used to pass the value |
| // through the continue block as %6 to OpPhi %28 as a block param. |
| TEST_F(SpirvParserTest, Phi_Propagated_PropagatedPhiValue_BlockParam) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %8 = OpTypeFunction %void |
| %int = OpTypeInt 32 1 |
| %1 = OpConstant %int 1 |
| %2 = OpConstant %int 2 |
| %3 = OpConstant %int 3 |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %8 |
| %22 = OpLabel |
| OpBranch %27 |
| %27 = OpLabel |
| %28 = OpPhi %int %2 %22 %29 %30 |
| %31 = OpPhi %int %1 %22 %3 %30 |
| OpLoopMerge %35 %30 None |
| OpBranchConditional %true %36 %35 |
| %36 = OpLabel |
| %29 = OpCopyObject %int %1 |
| OpSelectionMerge %40 None |
| OpBranchConditional %true %41 %40 |
| %41 = OpLabel |
| OpBranch %35 |
| %40 = OpLabel |
| OpBranch %30 |
| %30 = OpLabel |
| OpBranch %27 |
| %35 = OpLabel |
| %42 = OpPhi %int %28 %27 %29 %41 |
| %43 = OpIAdd %int %42 %42 |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:i32 = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration 2i, 1i # -> $B3 |
| } |
| $B3 (%3:i32, %4:i32): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| %5:i32 = let 1i |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| exit_loop %5 # loop_1 |
| } |
| $B8: { # false |
| exit_if # if_2 |
| } |
| } |
| continue %5 # -> $B4 |
| } |
| $B6: { # false |
| exit_loop %3 # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4 (%6:i32): { # continuing |
| next_iteration %6, 3i # -> $B3 |
| } |
| } |
| %7:i32 = spirv.add<i32> %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(SpirvParserTest, Phi_Propagated_BreakIf) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| %1 = OpExtInstImport "GLSL.std.450" |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %9 = OpTypeFunction %void |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %9 |
| %35 = OpLabel |
| OpBranch %39 |
| %39 = OpLabel |
| %40 = OpPhi %bool %true %35 %40 %41 |
| OpLoopMerge %44 %41 None |
| OpBranch %45 |
| %45 = OpLabel |
| OpSelectionMerge %49 None |
| OpBranchConditional %true %50 %49 |
| %50 = OpLabel |
| OpBranch %49 |
| %49 = OpLabel |
| OpBranch %41 |
| %41 = OpLabel |
| OpBranchConditional %true %39 %44 |
| %44 = OpLabel |
| OpBranch %55 |
| %55 = OpLabel |
| %56 = OpPhi %bool %40 %44 %true %57 |
| OpLoopMerge %59 %57 None |
| OpBranch %60 |
| %60 = OpLabel |
| OpBranch %57 |
| %57 = OpLabel |
| OpBranch %55 |
| %59 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:bool = loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration true # -> $B3 |
| } |
| $B3 (%3:bool): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| exit_if # if_1 |
| } |
| $B6: { # false |
| exit_if # if_1 |
| } |
| } |
| continue # -> $B4 |
| } |
| $B4: { # continuing |
| %4:bool = not true |
| break_if %4 next_iteration: [ %3 ] exit_loop: [ %3 ] # -> [t: exit_loop loop_1, f: $B3] |
| } |
| } |
| loop [i: $B7, b: $B8, c: $B9] { # loop_2 |
| $B7: { # initializer |
| next_iteration %2 # -> $B8 |
| } |
| $B8 (%5:bool): { # body |
| continue # -> $B9 |
| } |
| $B9: { # continuing |
| next_iteration true # -> $B8 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // Verify that %258 correctly propagates up to %259. %258 is defined in a selection construct |
| // headed by %211, and needs to be propagated out of that "if" first, and then propagated from the |
| // phi. |
| TEST_F(SpirvParserTest, Phi_Propagated_If) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %main "main" |
| OpExecutionMode %main OriginUpperLeft |
| OpName %main "main" |
| %void = OpTypeVoid |
| %10 = OpTypeFunction %void |
| %bool = OpTypeBool |
| %4 = OpConstantTrue %bool |
| %float = OpTypeFloat 32 |
| %1 = OpConstant %float 1 |
| %2 = OpConstant %float 2 |
| %3 = OpConstant %float 3 |
| %main = OpFunction %void None %10 |
| %58 = OpLabel |
| OpBranch %202 |
| %202 = OpLabel |
| OpSelectionMerge %209 None |
| OpBranchConditional %4 %210 %211 |
| %210 = OpLabel |
| OpBranch %209 |
| %211 = OpLabel |
| OpSelectionMerge %218 None |
| OpBranchConditional %4 %219 %220 |
| %219 = OpLabel |
| OpKill |
| %220 = OpLabel |
| OpSelectionMerge %222 None |
| OpBranchConditional %4 %223 %224 |
| %223 = OpLabel |
| OpBranch %222 |
| %224 = OpLabel |
| OpBranch %222 |
| %222 = OpLabel |
| %258 = OpPhi %float %1 %223 %2 %224 |
| OpBranch %218 |
| %218 = OpLabel |
| OpBranch %209 |
| %209 = OpLabel |
| %259 = OpPhi %float %3 %210 %258 %218 |
| %263 = OpFAdd %float %259 %259 |
| OpBranch %182 |
| %182 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| %2:f32 = if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| exit_if 3.0f # if_1 |
| } |
| $B3: { # false |
| %3:f32 = if true [t: $B4, f: $B5] { # if_2 |
| $B4: { # true |
| discard |
| ret |
| } |
| $B5: { # false |
| %4:f32 = if true [t: $B6, f: $B7] { # if_3 |
| $B6: { # true |
| exit_if 1.0f # if_3 |
| } |
| $B7: { # false |
| exit_if 2.0f # if_3 |
| } |
| } |
| exit_if %4 # if_2 |
| } |
| } |
| exit_if %3 # if_1 |
| } |
| } |
| %5:f32 = add %2, %2 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // The %34 is ignored and we propagate an undef because the continuing is unreachable |
| TEST_F(SpirvParserTest, Phi_FromContinuing) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint Fragment %1 "main" |
| OpExecutionMode %1 OriginUpperLeft |
| %void = OpTypeVoid |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %int = OpTypeInt 32 1 |
| %int_0 = OpConstant %int 0 |
| %int_1 = OpConstant %int 1 |
| %16 = OpTypeFunction %void |
| %1 = OpFunction %void None %16 |
| %17 = OpLabel |
| OpSelectionMerge %31 None |
| OpBranchConditional %true %32 %31 |
| %32 = OpLabel |
| %33 = OpPhi %int %int_0 %17 %34 %35 |
| OpLoopMerge %37 %35 None |
| OpBranchConditional %true %38 %37 |
| %38 = OpLabel |
| OpKill |
| %35 = OpLabel |
| %34 = OpCopyObject %int %int_1 |
| OpBranch %32 |
| %37 = OpLabel |
| OpBranch %31 |
| %31 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @fragment func():void { |
| $B1: { |
| if true [t: $B2, f: $B3] { # if_1 |
| $B2: { # true |
| loop [i: $B4, b: $B5, c: $B6] { # loop_1 |
| $B4: { # initializer |
| next_iteration 0i # -> $B5 |
| } |
| $B5 (%2:i32): { # body |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| discard |
| ret |
| } |
| $B8: { # false |
| exit_loop # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B6: { # continuing |
| next_iteration undef # -> $B5 |
| } |
| } |
| exit_if # if_1 |
| } |
| $B3: { # false |
| exit_if # if_1 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| // Bug: https://crbug.com/dawn/435621075 |
| TEST_F(SpirvParserTest, Phi_Bug_435621075) { |
| EXPECT_IR(R"( |
| OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| OpEntryPoint GLCompute %main "main" |
| OpExecutionMode %main LocalSize 1 1 1 |
| OpName %main "main" |
| %float = OpTypeFloat 32 |
| %float_0 = OpConstant %float 0 |
| %void = OpTypeVoid |
| %29 = OpTypeFunction %void |
| %bool = OpTypeBool |
| %true = OpConstantTrue %bool |
| %main = OpFunction %void None %29 |
| %33 = OpLabel |
| OpBranch %35 |
| %35 = OpLabel |
| %36 = OpPhi %float %float_0 %33 %37 %38 |
| OpLoopMerge %44 %38 None |
| OpBranchConditional %true %45 %44 |
| %45 = OpLabel |
| %37 = OpCopyObject %float %float_0 |
| OpSelectionMerge %48 None |
| OpBranchConditional %true %49 %48 |
| %49 = OpLabel |
| OpBranch %38 |
| %48 = OpLabel |
| OpBranch %38 |
| %38 = OpLabel |
| OpBranch %35 |
| %44 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )", |
| R"( |
| %main = @compute @workgroup_size(1u, 1u, 1u) func():void { |
| $B1: { |
| loop [i: $B2, b: $B3, c: $B4] { # loop_1 |
| $B2: { # initializer |
| next_iteration 0.0f # -> $B3 |
| } |
| $B3 (%2:f32): { # body |
| if true [t: $B5, f: $B6] { # if_1 |
| $B5: { # true |
| %3:f32 = let 0.0f |
| if true [t: $B7, f: $B8] { # if_2 |
| $B7: { # true |
| continue %3 # -> $B4 |
| } |
| $B8: { # false |
| exit_if # if_2 |
| } |
| } |
| continue %3 # -> $B4 |
| } |
| $B6: { # false |
| exit_loop # loop_1 |
| } |
| } |
| unreachable |
| } |
| $B4 (%4:f32): { # continuing |
| next_iteration %4 # -> $B3 |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| } // namespace |
| } // namespace tint::spirv::reader |