blob: 22a6610026d5d112ae51c55c007f48c3c4579774 [file] [log] [blame]
// 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_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
}
}
)");
}
} // namespace
} // namespace tint::spirv::reader