Range Analysis: Compute range of built-in call `max`
This patch computes the range of a core built-in call `max`.
- When both `a` and `b` are constant values, the range of
`max(a, b)` will also be a constant value.
- When the range of `a` is [min1, max1] and the range of `b` is
[min2, max2], the range of `max(a, b)` is `[max(min1, min2),
max(max1, max2)]`.
- The ranges that represent the whole range of `i32` or `u32`
are treated as invalid ones (e.g. max(a, i32::kLowestValue)).
Bug: 348701956
Test: tint_unittests
Change-Id: Id2c650b289da7221744c28ca92ca92d8c0a84b6b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/253576
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/analysis/integer_range_analysis.cc b/src/tint/lang/core/ir/analysis/integer_range_analysis.cc
index 533bc4d..cdd28a9 100644
--- a/src/tint/lang/core/ir/analysis/integer_range_analysis.cc
+++ b/src/tint/lang/core/ir/analysis/integer_range_analysis.cc
@@ -108,11 +108,6 @@
}
}
-template <typename IntegerRange>
-bool IntegerRangeIsConstantValue(const IntegerRange& range) {
- return range.min_bound == range.max_bound;
-}
-
} // namespace
IntegerRangeInfo::IntegerRangeInfo(int64_t min_bound, int64_t max_bound) {
@@ -310,6 +305,9 @@
case core::BuiltinFn::kMin: {
return ComputeIntegerRangeForBuiltinMin(call);
}
+ case core::BuiltinFn::kMax: {
+ return ComputeIntegerRangeForBuiltinMax(call);
+ }
default:
return {};
}
@@ -1153,13 +1151,6 @@
auto range1_i32 = std::get<IntegerRangeInfo::SignedIntegerRange>(range1.range);
auto range2_i32 = std::get<IntegerRangeInfo::SignedIntegerRange>(range2.range);
- // When both operands are constant values, we can return the constant value.
- if (IntegerRangeIsConstantValue(range1_i32) &&
- IntegerRangeIsConstantValue(range2_i32)) {
- int64_t constant_value = std::min(range1_i32.min_bound, range2_i32.min_bound);
- return IntegerRangeInfo(constant_value, constant_value);
- }
-
// When the range is all i32 or u32, we will treat it as an invalid range.
int64_t min_bound = std::min(range1_i32.min_bound, range2_i32.min_bound);
int64_t max_bound = std::min(range1_i32.max_bound, range2_i32.max_bound);
@@ -1175,13 +1166,6 @@
auto range1_u32 = std::get<IntegerRangeInfo::UnsignedIntegerRange>(range1.range);
auto range2_u32 = std::get<IntegerRangeInfo::UnsignedIntegerRange>(range2.range);
- // When both operands are constant values, we can return the constant value.
- if (IntegerRangeIsConstantValue(range1_u32) &&
- IntegerRangeIsConstantValue(range2_u32)) {
- uint64_t constant_value = std::min(range1_u32.min_bound, range2_u32.min_bound);
- return IntegerRangeInfo(constant_value, constant_value);
- }
-
// When the range is all i32 or u32, we will treat it as an invalid range.
uint64_t min_bound = std::min(range1_u32.min_bound, range2_u32.min_bound);
uint64_t max_bound = std::min(range1_u32.max_bound, range2_u32.max_bound);
@@ -1193,6 +1177,60 @@
}
}
+ IntegerRangeInfo ComputeIntegerRangeForBuiltinMax(const CoreBuiltinCall* call) {
+ TINT_ASSERT(call->Operands().Length() == 2u);
+
+ TINT_ASSERT(call->Operand(0)->Type()->IsIntegerScalar());
+ TINT_ASSERT(call->Operand(1)->Type()->IsIntegerScalar());
+
+ IntegerRangeInfo range1 = GetInfo(call->Operand(0));
+ IntegerRangeInfo range2 = GetInfo(call->Operand(1));
+ if (!range1.IsValid() && !range2.IsValid()) {
+ return {};
+ }
+
+ if (!range1.IsValid()) {
+ range1 = GetFullRangeWithSameIntegerRangeInfoType(range2);
+ }
+ if (!range2.IsValid()) {
+ range2 = GetFullRangeWithSameIntegerRangeInfoType(range1);
+ }
+
+ // range1: [min1, max1] range2: [min2, max2]
+ // The minimum value of (max(range1, range2)) is max(min1, min2) and
+ // The maximum value of (max(range1, range2)) is max(max1, max2).
+ if (std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(range1.range)) {
+ TINT_ASSERT(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(range2.range));
+
+ auto range1_i32 = std::get<IntegerRangeInfo::SignedIntegerRange>(range1.range);
+ auto range2_i32 = std::get<IntegerRangeInfo::SignedIntegerRange>(range2.range);
+
+ // When the range is all i32 or u32, we will treat it as an invalid range.
+ int64_t min_bound = std::max(range1_i32.min_bound, range2_i32.min_bound);
+ int64_t max_bound = std::max(range1_i32.max_bound, range2_i32.max_bound);
+ if (min_bound == i32::kLowestValue && max_bound == i32::kHighestValue) {
+ return {};
+ }
+
+ return IntegerRangeInfo(min_bound, max_bound);
+ } else {
+ TINT_ASSERT(
+ std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(range2.range));
+
+ auto range1_u32 = std::get<IntegerRangeInfo::UnsignedIntegerRange>(range1.range);
+ auto range2_u32 = std::get<IntegerRangeInfo::UnsignedIntegerRange>(range2.range);
+
+ // When the range is all i32 or u32, we will treat it as an invalid range.
+ uint64_t min_bound = std::max(range1_u32.min_bound, range2_u32.min_bound);
+ uint64_t max_bound = std::max(range1_u32.max_bound, range2_u32.max_bound);
+ if (min_bound == u32::kLowestValue && max_bound == u32::kHighestValue) {
+ return {};
+ }
+
+ return IntegerRangeInfo(min_bound, max_bound);
+ }
+ }
+
Hashmap<const FunctionParam*, Vector<IntegerRangeInfo, 3>, 4>
integer_function_param_range_info_map_;
Hashmap<const Var*, IntegerRangeInfo, 8> integer_var_range_info_map_;
diff --git a/src/tint/lang/core/ir/analysis/integer_range_analysis_test.cc b/src/tint/lang/core/ir/analysis/integer_range_analysis_test.cc
index 6b5718b..81ed7e4 100644
--- a/src/tint/lang/core/ir/analysis/integer_range_analysis_test.cc
+++ b/src/tint/lang/core/ir/analysis/integer_range_analysis_test.cc
@@ -15719,5 +15719,1370 @@
EXPECT_EQ(19u, range.max_bound);
}
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_F32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().f32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ call_max = b.Call<f32>(BuiltinFn::kMax, param, 1.0_f);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:f32):void {
+ $B1: {
+ %3:f32 = max %param, 1.0f
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, 1.0f)`)
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_Vector_I32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().vec4<i32>());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* vec4_const = b.Construct(ty.vec4<i32>(), 1_i, 2_i, 3_i, 4_i);
+ call_max = b.Call<vec4<i32>>(BuiltinFn::kMax, param, vec4_const);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:vec4<i32>):void {
+ $B1: {
+ %3:vec4<i32> = construct 1i, 2i, 3i, 4i
+ %4:vec4<i32> = max %param, %3
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, vec4i(1, 2, 3, 4))`)
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_Vector_U32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().vec2<u32>());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* vec2_const = b.Construct(ty.vec2<u32>(), 1_u, 2_u);
+ call_max = b.Call<vec2<u32>>(BuiltinFn::kMax, param, vec2_const);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:vec2<u32>):void {
+ $B1: {
+ %3:vec2<u32> = construct 1u, 2u
+ %4:vec2<u32> = max %param, %3
+ ret
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, vec2u(1, 2))`)
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_BothInvalidRange_I32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param1 = b.FunctionParam("param1", mod.Types().i32());
+ auto* param2 = b.FunctionParam("param2", mod.Types().i32());
+ func->AppendParam(param1);
+ func->AppendParam(param2);
+
+ b.Append(func->Block(), [&] {
+ call_max = b.Call<i32>(BuiltinFn::kMax, param1, param2);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param1:i32, %param2:i32):void {
+ $B1: {
+ %4:i32 = max %param1, %param2
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param1, param2)`)
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_BothInvalidRange_U32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param1 = b.FunctionParam("param1", mod.Types().u32());
+ auto* param2 = b.FunctionParam("param2", mod.Types().u32());
+ func->AppendParam(param1);
+ func->AppendParam(param2);
+
+ b.Append(func->Block(), [&] {
+ call_max = b.Call<u32>(BuiltinFn::kMax, param1, param2);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param1:u32, %param2:u32):void {
+ $B1: {
+ %4:u32 = max %param1, %param2
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param1, param2)`)
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_BothAreConstantValues_I32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = -4
+ idx = b.Var("idx", -4_i);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < -3
+ auto* binary = b.LessThan<bool>(b.Load(idx), -3_i);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ Var* idy = nullptr;
+ auto* loop2 = b.Loop();
+ b.Append(loop2->Initializer(), [&] {
+ // idy = 2
+ idy = b.Var("idy", 2_i);
+ b.NextIteration(loop2);
+ });
+ b.Append(loop2->Body(), [&] {
+ // idy < 3
+ auto* binary_inner = b.LessThan<bool>(b.Load(idy), 3_i);
+ auto* ifelse_inner = b.If(binary_inner);
+ b.Append(ifelse_inner->True(), [&] { b.ExitIf(ifelse_inner); });
+ b.Append(ifelse_inner->False(), [&] { b.ExitLoop(loop2); });
+ auto* loadx = b.Load(idx);
+ auto* loady = b.Load(idy);
+ // call_max = max(idx, idy);
+ call_max = b.Call<i32>(BuiltinFn::kMax, loadx, loady);
+ b.Continue(loop2);
+ });
+ b.Append(loop2->Continuing(), [&] {
+ // idy++
+ b.Store(idy, b.Add<i32>(b.Load(idy), 1_i));
+ b.NextIteration(loop2);
+ });
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, i32, read_write> = var -4i
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:i32 = load %idx
+ %4:bool = lt %3, -3i
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B7, b: $B8, c: $B9] { # loop_2
+ $B7: { # initializer
+ %idy:ptr<function, i32, read_write> = var 2i
+ next_iteration # -> $B8
+ }
+ $B8: { # body
+ %6:i32 = load %idy
+ %7:bool = lt %6, 3i
+ if %7 [t: $B10, f: $B11] { # if_2
+ $B10: { # true
+ exit_if # if_2
+ }
+ $B11: { # false
+ exit_loop # loop_2
+ }
+ }
+ %8:i32 = load %idx
+ %9:i32 = load %idy
+ %10:i32 = max %8, %9
+ continue # -> $B9
+ }
+ $B9: { # continuing
+ %11:i32 = load %idy
+ %12:i32 = add %11, 1i
+ store %idy, %12
+ next_iteration # -> $B8
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %13:i32 = load %idx
+ %14:i32 = add %13, 1i
+ store %idx, %14
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, idy)`)
+ // idx: [-4, -4] idy: [2, 2]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::SignedIntegerRange>(info.range);
+ EXPECT_EQ(2, range.min_bound);
+ EXPECT_EQ(2, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_BothAreConstantValues_U32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = 5u
+ idx = b.Var("idx", 5_u);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 6u
+ auto* binary = b.LessThan<bool>(b.Load(idx), 6_u);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ Var* idy = nullptr;
+ auto* loop2 = b.Loop();
+ b.Append(loop2->Initializer(), [&] {
+ // idy = 1u
+ idy = b.Var("idy", 1_u);
+ b.NextIteration(loop2);
+ });
+ b.Append(loop2->Body(), [&] {
+ // idy < 2u
+ auto* binary_inner = b.LessThan<bool>(b.Load(idy), 2_u);
+ auto* ifelse_inner = b.If(binary_inner);
+ b.Append(ifelse_inner->True(), [&] { b.ExitIf(ifelse_inner); });
+ b.Append(ifelse_inner->False(), [&] { b.ExitLoop(loop2); });
+ auto* loadx = b.Load(idx);
+ auto* loady = b.Load(idy);
+ // call_max = max(idx, idy);
+ call_max = b.Call<u32>(BuiltinFn::kMax, loadx, loady);
+ b.Continue(loop2);
+ });
+ b.Append(loop2->Continuing(), [&] {
+ // idy++
+ b.Store(idy, b.Add<u32>(b.Load(idy), 1_u));
+ b.NextIteration(loop2);
+ });
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var 5u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 6u
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B7, b: $B8, c: $B9] { # loop_2
+ $B7: { # initializer
+ %idy:ptr<function, u32, read_write> = var 1u
+ next_iteration # -> $B8
+ }
+ $B8: { # body
+ %6:u32 = load %idy
+ %7:bool = lt %6, 2u
+ if %7 [t: $B10, f: $B11] { # if_2
+ $B10: { # true
+ exit_if # if_2
+ }
+ $B11: { # false
+ exit_loop # loop_2
+ }
+ }
+ %8:u32 = load %idx
+ %9:u32 = load %idy
+ %10:u32 = max %8, %9
+ continue # -> $B9
+ }
+ $B9: { # continuing
+ %11:u32 = load %idy
+ %12:u32 = add %11, 1u
+ store %idy, %12
+ next_iteration # -> $B8
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %13:u32 = load %idx
+ %14:u32 = add %13, 1u
+ store %idx, %14
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, idy)`)
+ // idx: [5u, 5u] idy: [1u, 1u]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::UnsignedIntegerRange>(info.range);
+ EXPECT_EQ(5u, range.min_bound);
+ EXPECT_EQ(5u, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_BothValidRange_I32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = -4
+ idx = b.Var("idx", -4_i);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 9
+ auto* binary = b.LessThan<bool>(b.Load(idx), 9_i);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ Var* idy = nullptr;
+ auto* loop2 = b.Loop();
+ b.Append(loop2->Initializer(), [&] {
+ // idy = 4
+ idy = b.Var("idy", 4_i);
+ b.NextIteration(loop2);
+ });
+ b.Append(loop2->Body(), [&] {
+ // idy < 6
+ auto* binary_inner = b.LessThan<bool>(b.Load(idy), 6_i);
+ auto* ifelse_inner = b.If(binary_inner);
+ b.Append(ifelse_inner->True(), [&] { b.ExitIf(ifelse_inner); });
+ b.Append(ifelse_inner->False(), [&] { b.ExitLoop(loop2); });
+ auto* loadx = b.Load(idx);
+ auto* loady = b.Load(idy);
+ // call_max = max(idx, idy);
+ call_max = b.Call<i32>(BuiltinFn::kMax, loadx, loady);
+ b.Continue(loop2);
+ });
+ b.Append(loop2->Continuing(), [&] {
+ // idy++
+ b.Store(idy, b.Add<i32>(b.Load(idy), 1_i));
+ b.NextIteration(loop2);
+ });
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, i32, read_write> = var -4i
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:i32 = load %idx
+ %4:bool = lt %3, 9i
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B7, b: $B8, c: $B9] { # loop_2
+ $B7: { # initializer
+ %idy:ptr<function, i32, read_write> = var 4i
+ next_iteration # -> $B8
+ }
+ $B8: { # body
+ %6:i32 = load %idy
+ %7:bool = lt %6, 6i
+ if %7 [t: $B10, f: $B11] { # if_2
+ $B10: { # true
+ exit_if # if_2
+ }
+ $B11: { # false
+ exit_loop # loop_2
+ }
+ }
+ %8:i32 = load %idx
+ %9:i32 = load %idy
+ %10:i32 = max %8, %9
+ continue # -> $B9
+ }
+ $B9: { # continuing
+ %11:i32 = load %idy
+ %12:i32 = add %11, 1i
+ store %idy, %12
+ next_iteration # -> $B8
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %13:i32 = load %idx
+ %14:i32 = add %13, 1i
+ store %idx, %14
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, idy)`)
+ // idx: [-4, 8] idy: [4, 5]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::SignedIntegerRange>(info.range);
+ EXPECT_EQ(4, range.min_bound);
+ EXPECT_EQ(8, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_BothValidRange_U32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = 4u
+ idx = b.Var("idx", 4_u);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 9u
+ auto* binary = b.LessThan<bool>(b.Load(idx), 9_u);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ Var* idy = nullptr;
+ auto* loop2 = b.Loop();
+ b.Append(loop2->Initializer(), [&] {
+ // idy = 1u
+ idy = b.Var("idy", 1_u);
+ b.NextIteration(loop2);
+ });
+ b.Append(loop2->Body(), [&] {
+ // idy < 8u
+ auto* binary_inner = b.LessThan<bool>(b.Load(idy), 8_u);
+ auto* ifelse_inner = b.If(binary_inner);
+ b.Append(ifelse_inner->True(), [&] { b.ExitIf(ifelse_inner); });
+ b.Append(ifelse_inner->False(), [&] { b.ExitLoop(loop2); });
+ auto* loadx = b.Load(idx);
+ auto* loady = b.Load(idy);
+ // call_max = max(idx, idy);
+ call_max = b.Call<u32>(BuiltinFn::kMax, loadx, loady);
+ b.Continue(loop2);
+ });
+ b.Append(loop2->Continuing(), [&] {
+ // idy++
+ b.Store(idy, b.Add<u32>(b.Load(idy), 1_u));
+ b.NextIteration(loop2);
+ });
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func():void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var 4u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %3:u32 = load %idx
+ %4:bool = lt %3, 9u
+ if %4 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ loop [i: $B7, b: $B8, c: $B9] { # loop_2
+ $B7: { # initializer
+ %idy:ptr<function, u32, read_write> = var 1u
+ next_iteration # -> $B8
+ }
+ $B8: { # body
+ %6:u32 = load %idy
+ %7:bool = lt %6, 8u
+ if %7 [t: $B10, f: $B11] { # if_2
+ $B10: { # true
+ exit_if # if_2
+ }
+ $B11: { # false
+ exit_loop # loop_2
+ }
+ }
+ %8:u32 = load %idx
+ %9:u32 = load %idy
+ %10:u32 = max %8, %9
+ continue # -> $B9
+ }
+ $B9: { # continuing
+ %11:u32 = load %idy
+ %12:u32 = add %11, 1u
+ store %idy, %12
+ next_iteration # -> $B8
+ }
+ }
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %13:u32 = load %idx
+ %14:u32 = add %13, 1u
+ store %idx, %14
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, idy)`)
+ // idx: [4u, 8u] idy: [1u, 7u]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::UnsignedIntegerRange>(info.range);
+ EXPECT_EQ(4u, range.min_bound);
+ EXPECT_EQ(8u, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_FirstIsInvalidRange_InvalidResult_I32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* min_i32 = b.Constant(i32::Lowest());
+ call_max = b.Call<i32>(BuiltinFn::kMax, param, min_i32);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ %3:i32 = max %param, -2147483648i
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, min_i32)`)
+ // min_i32 = i32::kLowestValue, param: [i32::kLowestValue, i32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_FirstIsInvalidRange_InvalidResult_U32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* min_u32 = b.Constant(u32::Lowest());
+ call_max = b.Call<u32>(BuiltinFn::kMax, param, min_u32);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ %3:u32 = max %param, 0u
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, min_u32)`)
+ // min_u32 = u32::kLowestValue, param: [u32::kLowestValue, u32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_SecondIsInvalidRange_InvalidResult_I32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* min_i32 = b.Constant(i32::Lowest());
+ call_max = b.Call<i32>(BuiltinFn::kMax, min_i32, param);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ %3:i32 = max -2147483648i, %param
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(min_i32, param)`)
+ // min_i32 = i32::kLowestValue, param: [i32::kLowestValue, i32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Failure_SecondIsInvalidRange_InvalidResult_U32) {
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* min_u32 = b.Constant(u32::Lowest());
+ call_max = b.Call<u32>(BuiltinFn::kMax, min_u32, param);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ %3:u32 = max 0u, %param
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(min_u32, param)`)
+ // min_u32 = u32::kHighestValue, param: [u32::kLowestValue, u32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_FALSE(info.IsValid());
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_FirstIsInvalidRange_I32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = 1
+ idx = b.Var("idx", 1_i);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 10
+ auto* binary = b.LessThan<bool>(b.Load(idx), 10_i);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ // call_max = max(param, idx);
+ auto* loadx = b.Load(idx);
+ call_max = b.Call<i32>(BuiltinFn::kMax, param, loadx);
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, i32, read_write> = var 1i
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:i32 = load %idx
+ %5:bool = lt %4, 10i
+ if %5 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ %6:i32 = load %idx
+ %7:i32 = max %param, %6
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %8:i32 = load %idx
+ %9:i32 = add %8, 1i
+ store %idx, %9
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, idx)`)
+ // idx: [1, 9], param: [i32::kLowestValue, i32::kHighestValue]
+ // call_max: [1, i32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::SignedIntegerRange>(info.range);
+ EXPECT_EQ(1, range.min_bound);
+ EXPECT_EQ(i32::kHighestValue, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_FirstIsInvalidRange_U32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = 1u
+ idx = b.Var("idx", 1_u);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 10u
+ auto* binary = b.LessThan<bool>(b.Load(idx), 10_u);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ // call_max = max(param, idx);
+ auto* loadx = b.Load(idx);
+ call_max = b.Call<u32>(BuiltinFn::kMax, param, loadx);
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var 1u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:u32 = load %idx
+ %5:bool = lt %4, 10u
+ if %5 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ %6:u32 = load %idx
+ %7:u32 = max %param, %6
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %8:u32 = load %idx
+ %9:u32 = add %8, 1u
+ store %idx, %9
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(param, idx)`)
+ // idx: [1, 9], param: [u32::kLowestValue, u32::kHighestValue]
+ // call_max: [1, u32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::UnsignedIntegerRange>(info.range);
+ EXPECT_EQ(1u, range.min_bound);
+ EXPECT_EQ(u32::kHighestValue, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_SecondIsInvalidRange_I32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = -10
+ idx = b.Var("idx", -10_i);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < -2
+ auto* binary = b.LessThan<bool>(b.Load(idx), -2_i);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ // call_max = max(idx, param);
+ auto* loadx = b.Load(idx);
+ call_max = b.Call<i32>(BuiltinFn::kMax, loadx, param);
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, i32, read_write> = var -10i
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:i32 = load %idx
+ %5:bool = lt %4, -2i
+ if %5 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ %6:i32 = load %idx
+ %7:i32 = max %6, %param
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %8:i32 = load %idx
+ %9:i32 = add %8, 1i
+ store %idx, %9
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, param)`)
+ // idx: [-10, -3], param: [i32::kLowestValue, i32::kHighestValue]
+ // call_max: [-10, i32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::SignedIntegerRange>(info.range);
+ EXPECT_EQ(-10, range.min_bound);
+ EXPECT_EQ(i32::kHighestValue, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Success_SecondIsInvalidRange_U32) {
+ Var* idx = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] {
+ // idx = 10u
+ idx = b.Var("idx", 10_u);
+ b.NextIteration(loop);
+ });
+ b.Append(loop->Body(), [&] {
+ // idx < 20u
+ auto* binary = b.LessThan<bool>(b.Load(idx), 20_u);
+ auto* ifelse = b.If(binary);
+ b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
+ b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
+
+ // call_max = max(idx, param);
+ auto* loadx = b.Load(idx);
+ call_max = b.Call<u32>(BuiltinFn::kMax, loadx, param);
+
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ // idx++
+ b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
+ b.NextIteration(loop);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ %idx:ptr<function, u32, read_write> = var 10u
+ next_iteration # -> $B3
+ }
+ $B3: { # body
+ %4:u32 = load %idx
+ %5:bool = lt %4, 20u
+ if %5 [t: $B5, f: $B6] { # if_1
+ $B5: { # true
+ exit_if # if_1
+ }
+ $B6: { # false
+ exit_loop # loop_1
+ }
+ }
+ %6:u32 = load %idx
+ %7:u32 = max %6, %param
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ %8:u32 = load %idx
+ %9:u32 = add %8, 1u
+ store %idx, %9
+ next_iteration # -> $B3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(idx, param)`)
+ // idx: [10, 19], param: [u32::kLowestValue, u32::kHighestValue]
+ // call_max: [10, u32::kHighestValue]
+ const auto& info = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info.range));
+ const auto& range = std::get<IntegerRangeInfo::UnsignedIntegerRange>(info.range);
+ EXPECT_EQ(10u, range.min_bound);
+ EXPECT_EQ(u32::kHighestValue, range.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Builtin_Input_Success_I32) {
+ CoreBuiltinCall* call_min = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* bound1 = b.Constant(-5_i);
+ call_min = b.Call<i32>(BuiltinFn::kMin, bound1, param);
+ auto* bound2 = b.Constant(3_i);
+ call_max = b.Call<i32>(BuiltinFn::kMax, bound2, call_min);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ %3:i32 = min -5i, %param
+ %4:i32 = max 3i, %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_min` (`min(bound1, param)`)
+ // bound1: [-5, -5] param: [i32::kLowestValue, i32::kHighestValue]
+ // call_min: [i32::kLowestValue, -5]
+ const auto& info_call_min = analysis.GetInfo(call_min);
+ ASSERT_TRUE(info_call_min.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info_call_min.range));
+ const auto& range_call_min =
+ std::get<IntegerRangeInfo::SignedIntegerRange>(info_call_min.range);
+ EXPECT_EQ(i32::kLowestValue, range_call_min.min_bound);
+ EXPECT_EQ(-5, range_call_min.max_bound);
+
+ // Range of `call_max` (`max(bound2, call_min)`)
+ // bound2: [3, 3] call_min: [i32::kLowestValue, -5]
+ // call_max: [3, 3]
+ const auto& info_call_max = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info_call_max.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info_call_max.range));
+ const auto& range_call_max =
+ std::get<IntegerRangeInfo::SignedIntegerRange>(info_call_max.range);
+ EXPECT_EQ(3, range_call_max.min_bound);
+ EXPECT_EQ(3, range_call_max.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Max_Builtin_Input_Success_U32) {
+ CoreBuiltinCall* call_min = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* bound1 = b.Constant(5_u);
+ call_min = b.Call<u32>(BuiltinFn::kMin, bound1, param);
+ auto* bound2 = b.Constant(3_u);
+ call_max = b.Call<u32>(BuiltinFn::kMax, bound2, call_min);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ %3:u32 = min 5u, %param
+ %4:u32 = max 3u, %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_min` (`min(bound1, param)`)
+ // bound1: [5u, 5u] param: [u32::kLowestValue, u32::kHighestValue]
+ // call_min: [u32::kLowestValue, 5u]
+ const auto& info_call_min = analysis.GetInfo(call_min);
+ ASSERT_TRUE(info_call_min.IsValid());
+ ASSERT_TRUE(
+ std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info_call_min.range));
+ const auto& range_call_min =
+ std::get<IntegerRangeInfo::UnsignedIntegerRange>(info_call_min.range);
+ EXPECT_EQ(u32::kLowestValue, range_call_min.min_bound);
+ EXPECT_EQ(5u, range_call_min.max_bound);
+
+ // Range of `call_max` (`max(bound2, call_min)`)
+ // bound2: [3u, 3u] call_min: [u32::kLowestValue, 5u]
+ // call_max: [3u, 5u]
+ const auto& info_call_max = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info_call_max.IsValid());
+ ASSERT_TRUE(
+ std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info_call_max.range));
+ const auto& range_call_max =
+ std::get<IntegerRangeInfo::UnsignedIntegerRange>(info_call_max.range);
+ EXPECT_EQ(3u, range_call_max.min_bound);
+ EXPECT_EQ(5u, range_call_max.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Min_Builtin_Input_Success_I32) {
+ CoreBuiltinCall* call_min = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().i32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* bound1 = b.Constant(-3_i);
+ call_max = b.Call<i32>(BuiltinFn::kMax, bound1, param);
+ auto* bound2 = b.Constant(5_i);
+ call_min = b.Call<i32>(BuiltinFn::kMin, bound2, call_max);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:i32):void {
+ $B1: {
+ %3:i32 = max -3i, %param
+ %4:i32 = min 5i, %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(bound1, param)`)
+ // bound1: [-3, -3] param: [i32::kLowestValue, i32::kHighestValue]
+ // call_max: [-3, i32::kHighestValue]
+ const auto& info_call_max = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info_call_max.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info_call_max.range));
+ const auto& range_call_max =
+ std::get<IntegerRangeInfo::SignedIntegerRange>(info_call_max.range);
+ EXPECT_EQ(-3, range_call_max.min_bound);
+ EXPECT_EQ(i32::kHighestValue, range_call_max.max_bound);
+
+ // Range of `call_min` (`min(bound2, call_max)`)
+ // bound2: [5, 5] call_max: [-3, i32::kHighestValue]
+ // call_min: [-3, 5]
+ const auto& info_call_min = analysis.GetInfo(call_min);
+ ASSERT_TRUE(info_call_min.IsValid());
+ ASSERT_TRUE(std::holds_alternative<IntegerRangeInfo::SignedIntegerRange>(info_call_min.range));
+ const auto& range_call_min =
+ std::get<IntegerRangeInfo::SignedIntegerRange>(info_call_min.range);
+ EXPECT_EQ(-3, range_call_min.min_bound);
+ EXPECT_EQ(5, range_call_min.max_bound);
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, Builtin_Min_Builtin_Input_Success_U32) {
+ CoreBuiltinCall* call_min = nullptr;
+ CoreBuiltinCall* call_max = nullptr;
+
+ auto* func = b.Function("func", ty.void_());
+ auto* param = b.FunctionParam("param", mod.Types().u32());
+ func->AppendParam(param);
+
+ b.Append(func->Block(), [&] {
+ auto* bound1 = b.Constant(5_u);
+ call_max = b.Call<u32>(BuiltinFn::kMax, bound1, param);
+ auto* bound2 = b.Constant(3_u);
+ call_min = b.Call<u32>(BuiltinFn::kMin, bound2, call_max);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%func = func(%param:u32):void {
+ $B1: {
+ %3:u32 = max 5u, %param
+ %4:u32 = min 3u, %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+ EXPECT_EQ(Validate(mod), Success);
+
+ IntegerRangeAnalysis analysis(&mod);
+
+ // Range of `call_max` (`max(bound1, param)`)
+ // bound1: [5u, 5u] param: [u32::kLowestValue, u32::kHighestValue]
+ // call_max: [5, u32::kHighestValue]
+ const auto& info_call_max = analysis.GetInfo(call_max);
+ ASSERT_TRUE(info_call_max.IsValid());
+ ASSERT_TRUE(
+ std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info_call_max.range));
+ const auto& range_call_max =
+ std::get<IntegerRangeInfo::UnsignedIntegerRange>(info_call_max.range);
+ EXPECT_EQ(5u, range_call_max.min_bound);
+ EXPECT_EQ(u32::kHighestValue, range_call_max.max_bound);
+
+ // Range of `call_min` (`min(bound2, call_max)`)
+ // bound2: [3u, 3u] call_max: [5u, u32::kHighestValue]
+ // call_min: [3u, 3u]
+ const auto& info_call_min = analysis.GetInfo(call_min);
+ ASSERT_TRUE(info_call_min.IsValid());
+ ASSERT_TRUE(
+ std::holds_alternative<IntegerRangeInfo::UnsignedIntegerRange>(info_call_min.range));
+ const auto& range_call_min =
+ std::get<IntegerRangeInfo::UnsignedIntegerRange>(info_call_min.range);
+ EXPECT_EQ(3u, range_call_min.min_bound);
+ EXPECT_EQ(3u, range_call_min.max_bound);
+}
+
} // namespace
} // namespace tint::core::ir::analysis