blob: 055de572f4c33056db9bac31f88d07eb61d96a71 [file] [log] [blame]
// Copyright 2023 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/lang/core/ir/transform/merge_return.h"
#include <utility>
#include "src/tint/lang/core/ir/transform/test_helper.h"
namespace tint::ir::transform {
namespace {
using namespace tint::builtin::fluent_types; // NOLINT
using namespace tint::number_suffixes; // NOLINT
using IR_MergeReturnTest = TransformTest;
TEST_F(IR_MergeReturnTest, NoModify_SingleReturnInRootBlock) {
auto* in = b.FunctionParam(ty.i32());
auto* func = b.Function("foo", ty.i32());
func->SetParams({in});
b.With(func->Block(), [&] { b.Return(func, b.Add(ty.i32(), in, 1_i)); });
auto* src = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
%3:i32 = add %2, 1i
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, NoModify_SingleReturnInMergeBlock) {
auto* in = b.FunctionParam(ty.i32());
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({in});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
ifelse->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse->True(), [&] { b.ExitIf(ifelse, b.Add(ty.i32(), in, 1_i)); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse, b.Add(ty.i32(), in, 2_i)); });
b.Return(func, ifelse->Result(0));
});
auto* src = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
%3:i32 = if %4 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
%5:i32 = add %2, 1i
exit_if %5 # if_1
}
%b3 = block { # false
%6:i32 = add %2, 2i
exit_if %6 # if_1
}
}
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, NoModify_SingleReturnInNestedMergeBlock) {
auto* in = b.FunctionParam(ty.i32());
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({in});
b.With(func->Block(), [&] {
auto* swtch = b.Switch(in);
b.With(b.Case(swtch, {Switch::CaseSelector{}}), [&] { b.ExitSwitch(swtch); });
auto* l = b.Loop();
b.With(l->Body(), [&] { b.ExitLoop(l); });
auto* ifelse = b.If(cond);
ifelse->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse->True(), [&] { b.ExitIf(ifelse, b.Add(ty.i32(), in, 1_i)); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse, b.Add(ty.i32(), in, 2_i)); });
b.Return(func, ifelse->Result(0));
});
auto* src = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
switch %2 [c: (default, %b2)] { # switch_1
%b2 = block { # case
exit_switch # switch_1
}
}
loop [b: %b3] { # loop_1
%b3 = block { # body
exit_loop # loop_1
}
}
%3:i32 = if %4 [t: %b4, f: %b5] { # if_1
%b4 = block { # true
%5:i32 = add %2, 1i
exit_if %5 # if_1
}
%b5 = block { # false
%6:i32 = add %2, 2i
exit_if %6 # if_1
}
}
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_OneSideReturns) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.void_());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Return(func);
});
auto* src = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret
}
%b3 = block { # false
exit_if # if_1
}
}
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
exit_if # if_1
}
%b3 = block { # false
exit_if # if_1
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
// This is the same as the above tests, but we create the return instructions in a different order
// to make sure that creation order doesn't matter.
TEST_F(IR_MergeReturnTest, IfElse_OneSideReturns_ReturnsCreatedInDifferentOrder) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.void_());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.Return(func);
b.With(ifelse->True(), [&] { b.Return(func); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
});
auto* src = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret
}
%b3 = block { # false
exit_if # if_1
}
}
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
exit_if # if_1
}
%b3 = block { # false
exit_if # if_1
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_OneSideReturns_WithValue) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Return(func, 2_i);
});
auto* src = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret 1i
}
%b3 = block { # false
exit_if # if_1
}
}
ret 2i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if # if_1
}
%b3 = block { # false
exit_if # if_1
}
}
%5:bool = load %continue_execution
if %5 [t: %b4] { # if_2
%b4 = block { # true
store %return_value, 2i
exit_if # if_2
}
}
%6:i32 = load %return_value
ret %6
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_OneSideReturns_WithValue_MergeHasBasicBlockArguments) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
ifelse->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse, 2_i); });
b.Return(func, ifelse->Result(0));
});
auto* src = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
%3:i32 = if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret 1i
}
%b3 = block { # false
exit_if 2i # if_1
}
}
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
%5:i32 = if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if undef # if_1
}
%b3 = block { # false
exit_if 2i # if_1
}
}
%6:bool = load %continue_execution
if %6 [t: %b4] { # if_2
%b4 = block { # true
store %return_value, %5
exit_if # if_2
}
}
%7:i32 = load %return_value
ret %7
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_OneSideReturns_WithValue_MergeHasUndefBasicBlockArguments) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
ifelse->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse, nullptr); });
b.Return(func, ifelse->Result(0));
});
auto* src = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
%3:i32 = if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret 1i
}
%b3 = block { # false
exit_if undef # if_1
}
}
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
%5:i32 = if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if undef # if_1
}
%b3 = block { # false
exit_if undef # if_1
}
}
%6:bool = load %continue_execution
if %6 [t: %b4] { # if_2
%b4 = block { # true
store %return_value, %5
exit_if # if_2
}
}
%7:i32 = load %return_value
ret %7
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_BothSidesReturn) {
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.void_());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func); });
b.With(ifelse->False(), [&] { b.Return(func); });
b.Unreachable();
});
auto* src = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
ret
}
%b3 = block { # false
ret
}
}
unreachable
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:bool):void -> %b1 {
%b1 = block {
if %2 [t: %b2, f: %b3] { # if_1
%b2 = block { # true
exit_if # if_1
}
%b3 = block { # false
exit_if # if_1
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_ThenStatements) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.void_());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Store(global, 42_i);
b.Return(func);
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):void -> %b2 {
%b2 = block {
if %3 [t: %b3, f: %b4] { # if_1
%b3 = block { # true
ret
}
%b4 = block { # false
exit_if # if_1
}
}
store %1, 42i
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):void -> %b2 {
%b2 = block {
%continue_execution:ptr<function, bool, read_write> = var, true
if %3 [t: %b3, f: %b4] { # if_1
%b3 = block { # true
store %continue_execution, false
exit_if # if_1
}
%b4 = block { # false
exit_if # if_1
}
}
%5:bool = load %continue_execution
if %5 [t: %b5] { # if_2
%b5 = block { # true
store %1, 42i
exit_if # if_2
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
// This is the same as the above tests, but we create the return instructions in a different order
// to make sure that creation order doesn't matter.
TEST_F(IR_MergeReturnTest, IfElse_ThenStatements_ReturnsCreatedInDifferentOrder) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.void_());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* ifelse = b.If(cond);
b.Store(global, 42_i);
b.Return(func);
b.With(ifelse->True(), [&] { b.Return(func); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):void -> %b2 {
%b2 = block {
if %3 [t: %b3, f: %b4] { # if_1
%b3 = block { # true
ret
}
%b4 = block { # false
exit_if # if_1
}
}
store %1, 42i
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):void -> %b2 {
%b2 = block {
%continue_execution:ptr<function, bool, read_write> = var, true
if %3 [t: %b3, f: %b4] { # if_1
%b3 = block { # true
store %continue_execution, false
exit_if # if_1
}
%b4 = block { # false
exit_if # if_1
}
}
%5:bool = load %continue_execution
if %5 [t: %b5] { # if_2
%b5 = block { # true
store %1, 42i
exit_if # if_2
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_Nested) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* func = b.Function("foo", ty.i32());
auto* condA = b.FunctionParam("condA", ty.bool_());
auto* condB = b.FunctionParam("condB", ty.bool_());
auto* condC = b.FunctionParam("condC", ty.bool_());
func->SetParams({condA, condB, condC});
b.With(func->Block(), [&] {
auto* ifelse_outer = b.If(condA);
b.With(ifelse_outer->True(), [&] { b.Return(func, 3_i); });
b.With(ifelse_outer->False(), [&] {
auto* ifelse_middle = b.If(condB);
b.With(ifelse_middle->True(), [&] {
auto* ifelse_inner = b.If(condC);
b.With(ifelse_inner->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse_inner->False(), [&] { b.ExitIf(ifelse_inner); });
b.Store(global, 1_i);
b.Return(func, 2_i);
});
b.With(ifelse_middle->False(), [&] { b.ExitIf(ifelse_middle); });
b.Store(global, 2_i);
b.ExitIf(ifelse_outer);
});
b.Store(global, 3_i);
b.Return(func, b.Add(ty.i32(), 5_i, 6_i));
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
ret 3i
}
%b4 = block { # false
if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
ret 1i
}
%b8 = block { # false
exit_if # if_3
}
}
store %1, 1i
ret 2i
}
%b6 = block { # false
exit_if # if_2
}
}
store %1, 2i
exit_if # if_1
}
}
store %1, 3i
%6:i32 = add 5i, 6i
ret %6
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
store %continue_execution, false
store %return_value, 3i
exit_if # if_1
}
%b4 = block { # false
if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if # if_3
}
%b8 = block { # false
exit_if # if_3
}
}
%8:bool = load %continue_execution
if %8 [t: %b9] { # if_4
%b9 = block { # true
store %1, 1i
store %continue_execution, false
store %return_value, 2i
exit_if # if_4
}
}
exit_if # if_2
}
%b6 = block { # false
exit_if # if_2
}
}
%9:bool = load %continue_execution
if %9 [t: %b10] { # if_5
%b10 = block { # true
store %1, 2i
exit_if # if_5
}
}
exit_if # if_1
}
}
%10:bool = load %continue_execution
if %10 [t: %b11] { # if_6
%b11 = block { # true
store %1, 3i
%11:i32 = add 5i, 6i
store %return_value, %11
exit_if # if_6
}
}
%12:i32 = load %return_value
ret %12
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_Nested_TrivialMerge) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* func = b.Function("foo", ty.i32());
auto* condA = b.FunctionParam("condA", ty.bool_());
auto* condB = b.FunctionParam("condB", ty.bool_());
auto* condC = b.FunctionParam("condC", ty.bool_());
func->SetParams({condA, condB, condC});
b.With(func->Block(), [&] {
auto* ifelse_outer = b.If(condA);
b.With(ifelse_outer->True(), [&] { b.Return(func, 3_i); });
b.With(ifelse_outer->False(), [&] {
auto* ifelse_middle = b.If(condB);
b.With(ifelse_middle->True(), [&] {
auto* ifelse_inner = b.If(condC);
b.With(ifelse_inner->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse_inner->False(), [&] { b.ExitIf(ifelse_inner); });
b.ExitIf(ifelse_middle);
});
b.With(ifelse_middle->False(), [&] { b.ExitIf(ifelse_middle); });
b.ExitIf(ifelse_outer);
});
b.Return(func, 3_i);
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
ret 3i
}
%b4 = block { # false
if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
ret 1i
}
%b8 = block { # false
exit_if # if_3
}
}
exit_if # if_2
}
%b6 = block { # false
exit_if # if_2
}
}
exit_if # if_1
}
}
ret 3i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
store %continue_execution, false
store %return_value, 3i
exit_if # if_1
}
%b4 = block { # false
if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if # if_3
}
%b8 = block { # false
exit_if # if_3
}
}
exit_if # if_2
}
%b6 = block { # false
exit_if # if_2
}
}
exit_if # if_1
}
}
%8:bool = load %continue_execution
if %8 [t: %b9] { # if_4
%b9 = block { # true
store %return_value, 3i
exit_if # if_4
}
}
%9:i32 = load %return_value
ret %9
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, IfElse_Nested_WithBasicBlockArguments) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* func = b.Function("foo", ty.i32());
auto* condA = b.FunctionParam("condA", ty.bool_());
auto* condB = b.FunctionParam("condB", ty.bool_());
auto* condC = b.FunctionParam("condC", ty.bool_());
func->SetParams({condA, condB, condC});
b.With(func->Block(), [&] {
auto* ifelse_outer = b.If(condA);
ifelse_outer->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse_outer->True(), [&] { b.Return(func, 3_i); });
b.With(ifelse_outer->False(), [&] {
auto* ifelse_middle = b.If(condB);
ifelse_middle->SetResults(b.InstructionResult(ty.i32()));
b.With(ifelse_middle->True(), [&] {
auto* ifelse_inner = b.If(condC);
b.With(ifelse_inner->True(), [&] { b.Return(func, 1_i); });
b.With(ifelse_inner->False(), [&] { b.ExitIf(ifelse_inner); });
b.ExitIf(ifelse_middle, b.Add(ty.i32(), 42_i, 1_i));
});
b.With(ifelse_middle->False(),
[&] { b.ExitIf(ifelse_middle, b.Add(ty.i32(), 43_i, 2_i)); });
b.ExitIf(ifelse_outer, b.Add(ty.i32(), ifelse_middle->Result(0), 1_i));
});
b.Return(func, b.Add(ty.i32(), ifelse_outer->Result(0), 1_i));
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
%6:i32 = if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
ret 3i
}
%b4 = block { # false
%7:i32 = if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
ret 1i
}
%b8 = block { # false
exit_if # if_3
}
}
%8:i32 = add 42i, 1i
exit_if %8 # if_2
}
%b6 = block { # false
%9:i32 = add 43i, 2i
exit_if %9 # if_2
}
}
%10:i32 = add %7, 1i
exit_if %10 # if_1
}
}
%11:i32 = add %6, 1i
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%condA:bool, %condB:bool, %condC:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
%8:i32 = if %condA [t: %b3, f: %b4] { # if_1
%b3 = block { # true
store %continue_execution, false
store %return_value, 3i
exit_if undef # if_1
}
%b4 = block { # false
%9:i32 = if %condB [t: %b5, f: %b6] { # if_2
%b5 = block { # true
if %condC [t: %b7, f: %b8] { # if_3
%b7 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if # if_3
}
%b8 = block { # false
exit_if # if_3
}
}
%10:bool = load %continue_execution
%11:i32 = if %10 [t: %b9] { # if_4
%b9 = block { # true
%12:i32 = add 42i, 1i
exit_if %12 # if_4
}
# implicit false block: exit_if undef
}
exit_if %11 # if_2
}
%b6 = block { # false
%13:i32 = add 43i, 2i
exit_if %13 # if_2
}
}
%14:bool = load %continue_execution
%15:i32 = if %14 [t: %b10] { # if_5
%b10 = block { # true
%16:i32 = add %9, 1i
exit_if %16 # if_5
}
# implicit false block: exit_if undef
}
exit_if %15 # if_1
}
}
%17:bool = load %continue_execution
if %17 [t: %b11] { # if_6
%b11 = block { # true
%18:i32 = add %8, 1i
store %return_value, %18
exit_if # if_6
}
}
%19:i32 = load %return_value
ret %19
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Loop_UnconditionalReturnInBody) {
auto* func = b.Function("foo", ty.i32());
b.With(func->Block(), [&] {
auto* loop = b.Loop();
b.With(loop->Body(), [&] { b.Return(func, 42_i); });
b.Unreachable();
});
auto* src = R"(
%foo = func():i32 -> %b1 {
%b1 = block {
loop [b: %b2] { # loop_1
%b2 = block { # body
ret 42i
}
}
unreachable
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
loop [b: %b2] { # loop_1
%b2 = block { # body
store %return_value, 42i
exit_loop # loop_1
}
}
%3:i32 = load %return_value
ret %3
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Loop_ConditionalReturnInBody) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* loop = b.Loop();
b.With(loop->Body(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func, 42_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Store(global, 2_i);
b.Continue(loop);
});
b.With(loop->Continuing(), [&] {
b.Store(global, 1_i);
b.BreakIf(loop, true);
});
b.Store(global, 3_i);
b.Return(func, 43_i);
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
ret 42i
}
%b6 = block { # false
exit_if # if_1
}
}
store %1, 2i
continue %b4
}
%b4 = block { # continuing
store %1, 1i
break_if true %b3
}
}
store %1, 3i
ret 43i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
store %continue_execution, false
store %return_value, 42i
exit_if # if_1
}
%b6 = block { # false
exit_if # if_1
}
}
%6:bool = load %continue_execution
if %6 [t: %b7] { # if_2
%b7 = block { # true
store %1, 2i
continue %b4
}
}
exit_loop # loop_1
}
%b4 = block { # continuing
store %1, 1i
break_if true %b3
}
}
%7:bool = load %continue_execution
if %7 [t: %b8] { # if_3
%b8 = block { # true
store %1, 3i
store %return_value, 43i
exit_if # if_3
}
}
%8:i32 = load %return_value
ret %8
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Loop_ConditionalReturnInBody_UnreachableMerge) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* loop = b.Loop();
b.With(loop->Body(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func, 42_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Store(global, 2_i);
b.Continue(loop);
});
b.With(loop->Continuing(), [&] {
b.Store(global, 1_i);
b.NextIteration(loop);
});
b.Unreachable();
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
ret 42i
}
%b6 = block { # false
exit_if # if_1
}
}
store %1, 2i
continue %b4
}
%b4 = block { # continuing
store %1, 1i
next_iteration %b3
}
}
unreachable
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
store %continue_execution, false
store %return_value, 42i
exit_if # if_1
}
%b6 = block { # false
exit_if # if_1
}
}
%6:bool = load %continue_execution
if %6 [t: %b7] { # if_2
%b7 = block { # true
store %1, 2i
continue %b4
}
}
exit_loop # loop_1
}
%b4 = block { # continuing
store %1, 1i
next_iteration %b3
}
}
%7:i32 = load %return_value
ret %7
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, DISABLED_Loop_WithBasicBlockArgumentsOnMerge) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.bool_());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* loop = b.Loop();
loop->SetResults(b.InstructionResult(ty.i32()));
b.With(loop->Body(), [&] {
auto* ifelse = b.If(cond);
b.With(ifelse->True(), [&] { b.Return(func, 42_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Store(global, 2_i);
b.Continue(loop);
});
b.With(loop->Continuing(), [&] {
b.Store(global, 1_i);
b.BreakIf(loop, true, 4_i);
});
b.Store(global, 3_i);
b.Return(func, loop->Result(0));
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
%4:i32 = loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
ret 42i
}
%b6 = block { # false
exit_if # if_1
}
}
store %1, 2i
continue %b4
}
%b4 = block { # continuing
store %1, 1i
break_if true %b3 4i
}
}
store %1, 3i
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:bool):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
%6:i32 = loop [b: %b3, c: %b4] { # loop_1
%b3 = block { # body
if %3 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
store %continue_execution, false
store %return_value, 42i
exit_if # if_1
}
%b6 = block { # false
exit_if # if_1
}
}
%7:bool = load %continue_execution
if %7 [t: %b7] { # if_2
%b7 = block { # true
store %1, 2i
continue %b4
}
}
exit_loop # loop_1
}
%b4 = block { # continuing
store %1, 1i
break_if true %b3 4i
}
}
%8:bool = load %continue_execution
if %8 [t: %b8] { # if_3
%b8 = block { # true
store %1, 3i
store %return_value, %6
exit_if # if_3
}
}
%9:i32 = load %return_value
ret %9
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Switch_UnconditionalReturnInCase) {
auto* cond = b.FunctionParam(ty.i32());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* sw = b.Switch(cond);
b.With(b.Case(sw, {Switch::CaseSelector{b.Constant(1_i)}}), [&] { b.Return(func, 42_i); });
b.With(b.Case(sw, {Switch::CaseSelector{}}), [&] { b.ExitSwitch(sw); });
b.Return(func, 0_i);
});
auto* src = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
switch %2 [c: (1i, %b2), c: (default, %b3)] { # switch_1
%b2 = block { # case
ret 42i
}
%b3 = block { # case
exit_switch # switch_1
}
}
ret 0i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
switch %2 [c: (1i, %b2), c: (default, %b3)] { # switch_1
%b2 = block { # case
store %continue_execution, false
store %return_value, 42i
exit_switch # switch_1
}
%b3 = block { # case
exit_switch # switch_1
}
}
%5:bool = load %continue_execution
if %5 [t: %b4] { # if_1
%b4 = block { # true
store %return_value, 0i
exit_if # if_1
}
}
%6:i32 = load %return_value
ret %6
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Switch_ConditionalReturnInBody) {
auto* global = b.Var(ty.ptr<private_, i32>());
b.RootBlock()->Append(global);
auto* cond = b.FunctionParam(ty.i32());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* sw = b.Switch(cond);
b.With(b.Case(sw, {Switch::CaseSelector{b.Constant(1_i)}}), [&] {
auto* ifcond = b.Equal(ty.bool_(), cond, 1_i);
auto* ifelse = b.If(ifcond);
b.With(ifelse->True(), [&] { b.Return(func, 42_i); });
b.With(ifelse->False(), [&] { b.ExitIf(ifelse); });
b.Store(global, 2_i);
b.ExitSwitch(sw);
});
b.With(b.Case(sw, {Switch::CaseSelector{}}), [&] { b.ExitSwitch(sw); });
b.Return(func, 0_i);
});
auto* src = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:i32):i32 -> %b2 {
%b2 = block {
switch %3 [c: (1i, %b3), c: (default, %b4)] { # switch_1
%b3 = block { # case
%4:bool = eq %3, 1i
if %4 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
ret 42i
}
%b6 = block { # false
exit_if # if_1
}
}
store %1, 2i
exit_switch # switch_1
}
%b4 = block { # case
exit_switch # switch_1
}
}
ret 0i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%b1 = block { # root
%1:ptr<private, i32, read_write> = var
}
%foo = func(%3:i32):i32 -> %b2 {
%b2 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
switch %3 [c: (1i, %b3), c: (default, %b4)] { # switch_1
%b3 = block { # case
%6:bool = eq %3, 1i
if %6 [t: %b5, f: %b6] { # if_1
%b5 = block { # true
store %continue_execution, false
store %return_value, 42i
exit_if # if_1
}
%b6 = block { # false
exit_if # if_1
}
}
%7:bool = load %continue_execution
if %7 [t: %b7] { # if_2
%b7 = block { # true
store %1, 2i
exit_switch # switch_1
}
}
exit_switch # switch_1
}
%b4 = block { # case
exit_switch # switch_1
}
}
%8:bool = load %continue_execution
if %8 [t: %b8] { # if_3
%b8 = block { # true
store %return_value, 0i
exit_if # if_3
}
}
%9:i32 = load %return_value
ret %9
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, Switch_WithBasicBlockArgumentsOnMerge) {
auto* cond = b.FunctionParam(ty.i32());
auto* func = b.Function("foo", ty.i32());
func->SetParams({cond});
b.With(func->Block(), [&] {
auto* sw = b.Switch(cond);
sw->SetResults(b.InstructionResult(ty.i32())); // NOLINT: false detection of std::tuple
b.With(b.Case(sw, {Switch::CaseSelector{b.Constant(1_i)}}), [&] { b.Return(func, 42_i); });
b.With(b.Case(sw, {Switch::CaseSelector{b.Constant(2_i)}}), [&] { b.Return(func, 99_i); });
b.With(b.Case(sw, {Switch::CaseSelector{b.Constant(3_i)}}), [&] { b.ExitSwitch(sw, 1_i); });
b.With(b.Case(sw, {Switch::CaseSelector{}}), [&] { b.ExitSwitch(sw, 0_i); });
b.Return(func, sw->Result(0));
});
auto* src = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
%3:i32 = switch %2 [c: (1i, %b2), c: (2i, %b3), c: (3i, %b4), c: (default, %b5)] { # switch_1
%b2 = block { # case
ret 42i
}
%b3 = block { # case
ret 99i
}
%b4 = block { # case
exit_switch 1i # switch_1
}
%b5 = block { # case
exit_switch 0i # switch_1
}
}
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%2:i32):i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
%5:i32 = switch %2 [c: (1i, %b2), c: (2i, %b3), c: (3i, %b4), c: (default, %b5)] { # switch_1
%b2 = block { # case
store %continue_execution, false
store %return_value, 42i
exit_switch undef # switch_1
}
%b3 = block { # case
store %continue_execution, false
store %return_value, 99i
exit_switch undef # switch_1
}
%b4 = block { # case
exit_switch 1i # switch_1
}
%b5 = block { # case
exit_switch 0i # switch_1
}
}
%6:bool = load %continue_execution
if %6 [t: %b6] { # if_1
%b6 = block { # true
store %return_value, %5
exit_if # if_1
}
}
%7:i32 = load %return_value
ret %7
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, LoopIfReturnThenContinue) {
auto* func = b.Function("foo", ty.void_());
b.With(func->Block(), [&] {
auto* loop = b.Loop();
b.With(loop->Body(), [&] {
b.With(b.If(true)->True(), [&] { b.Return(func); });
b.Continue(loop);
});
b.Unreachable();
});
auto* src = R"(
%foo = func():void -> %b1 {
%b1 = block {
loop [b: %b2] { # loop_1
%b2 = block { # body
if true [t: %b3] { # if_1
%b3 = block { # true
ret
}
}
continue %b4
}
}
unreachable
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():void -> %b1 {
%b1 = block {
%continue_execution:ptr<function, bool, read_write> = var, true
loop [b: %b2] { # loop_1
%b2 = block { # body
if true [t: %b3] { # if_1
%b3 = block { # true
store %continue_execution, false
exit_if # if_1
}
}
%3:bool = load %continue_execution
if %3 [t: %b4] { # if_2
%b4 = block { # true
continue %b5
}
}
exit_loop # loop_1
}
}
ret
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
TEST_F(IR_MergeReturnTest, NestedIfsWithReturns) {
auto* func = b.Function("foo", ty.i32());
b.With(func->Block(), [&] {
b.With(b.If(true)->True(), [&] {
b.With(b.If(true)->True(), [&] { b.Return(func, 1_i); });
b.Return(func, 2_i);
});
b.Return(func, 3_i);
});
auto* src = R"(
%foo = func():i32 -> %b1 {
%b1 = block {
if true [t: %b2] { # if_1
%b2 = block { # true
if true [t: %b3] { # if_2
%b3 = block { # true
ret 1i
}
}
ret 2i
}
}
ret 3i
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():i32 -> %b1 {
%b1 = block {
%return_value:ptr<function, i32, read_write> = var
%continue_execution:ptr<function, bool, read_write> = var, true
if true [t: %b2] { # if_1
%b2 = block { # true
if true [t: %b3] { # if_2
%b3 = block { # true
store %continue_execution, false
store %return_value, 1i
exit_if # if_2
}
}
%4:bool = load %continue_execution
if %4 [t: %b4] { # if_3
%b4 = block { # true
store %continue_execution, false
store %return_value, 2i
exit_if # if_3
}
}
exit_if # if_1
}
}
%5:bool = load %continue_execution
if %5 [t: %b5] { # if_4
%b5 = block { # true
store %return_value, 3i
exit_if # if_4
}
}
%6:i32 = load %return_value
ret %6
}
}
)";
Run<MergeReturn>();
EXPECT_EQ(expect, str());
}
} // namespace
} // namespace tint::ir::transform