Range Analysis: Check loop continuing block

This patch is the second part to implement the computation of the
range on the loop control variables. In this patch we implement the
function `GetBinaryToUpdateLoopControlVariableInContinuingBlock()`
to get the binary to update the loop control variable in the loop
continuing block if the loop continuing block meets the requirements
of current range analysis algorithm (a straightforward for-loop):
- Only one load and one store instruction on the loop control
  variable got in the loop initializer.
- Only accept plus one or minus one to the loop control variable.

Bug: chromium:348701956
Test: tint_unittests
Change-Id: I20ec096ab14d6d7bc098e92bcd731d0195d963e6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/222074
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.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 56213f1..1e7384d 100644
--- a/src/tint/lang/core/ir/analysis/integer_range_analysis.cc
+++ b/src/tint/lang/core/ir/analysis/integer_range_analysis.cc
@@ -30,15 +30,36 @@
 #include <limits>
 
 #include "src/tint/lang/core/constant/scalar.h"
+#include "src/tint/lang/core/ir/binary.h"
 #include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/next_iteration.h"
+#include "src/tint/lang/core/ir/store.h"
 #include "src/tint/lang/core/ir/traverse.h"
 #include "src/tint/lang/core/ir/var.h"
+#include "src/tint/lang/core/type/i32.h"
 #include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/core/type/u32.h"
+#include "src/tint/utils/rtti/switch.h"
 
 namespace tint::core::ir::analysis {
 
+namespace {
+/// Returns true if v is the integer constant 1.
+bool IsOne(const Value* v) {
+    if (auto* cv = v->As<Constant>()) {
+        return Switch(
+            cv->Type(),
+            [&](const core::type::I32*) { return cv->Value()->ValueAs<int32_t>() == 1; },
+            [&](const core::type::U32*) { return cv->Value()->ValueAs<uint32_t>() == 1; },
+            [&](const Default) -> bool { return false; });
+    }
+    return false;
+}
+}  // namespace
+
 IntegerRangeInfo::IntegerRangeInfo(int64_t min_bound, int64_t max_bound) {
     TINT_ASSERT(min_bound <= max_bound);
     range = SignedIntegerRange{min_bound, max_bound};
@@ -100,6 +121,8 @@
     }
 
     const Var* GetLoopControlVariableFromConstantInitializer(const Loop* loop) {
+        TINT_ASSERT(loop);
+
         auto* init_block = loop->Initializer();
         if (!init_block) {
             return nullptr;
@@ -141,6 +164,97 @@
         return var;
     }
 
+    // Currently we only support the loop continuing of a simple for-loop, which only has 4
+    // instructions
+    /// - The first instruction is to load the loop control variable into a temporary variable.
+    /// - The second instruction is to add one or minus one to the temporary variable.
+    /// - The third instruction is to store the value of the temporary variable into the loop
+    ///   control variable.
+    /// - The fourth instruction is `next_iteration`.
+    const Binary* GetBinaryToUpdateLoopControlVariableInContinuingBlock(
+        const Loop* loop,
+        const Var* loop_control_variable) {
+        TINT_ASSERT(loop);
+        TINT_ASSERT(loop_control_variable);
+
+        auto* continuing_block = loop->Continuing();
+        if (!continuing_block) {
+            return nullptr;
+        }
+
+        if (continuing_block->Length() != 4u) {
+            return nullptr;
+        }
+
+        // 1st instruction:
+        // %src = load %loop_control_variable
+        const auto* load_from_loop_control_variable = continuing_block->Instructions()->As<Load>();
+        if (!load_from_loop_control_variable) {
+            return nullptr;
+        }
+        if (load_from_loop_control_variable->From() != loop_control_variable->Result(0)) {
+            return nullptr;
+        }
+
+        // 2nd instruction:
+        // %dst = add %src, 1
+        // or %dst = add 1, %src
+        // or %dst = sub %src, 1
+        const auto* add_or_sub_from_loop_control_variable =
+            load_from_loop_control_variable->next->As<Binary>();
+        if (!add_or_sub_from_loop_control_variable) {
+            return nullptr;
+        }
+        const auto* src = load_from_loop_control_variable->Result(0);
+        const auto* lhs = add_or_sub_from_loop_control_variable->LHS();
+        const auto* rhs = add_or_sub_from_loop_control_variable->RHS();
+        switch (add_or_sub_from_loop_control_variable->Op()) {
+            case BinaryOp::kAdd: {
+                // %dst = add %src, 1
+                if (lhs == src && IsOne(rhs)) {
+                    break;
+                }
+                // %dst = add 1, %src
+                if (rhs == src && IsOne(lhs)) {
+                    break;
+                }
+                return nullptr;
+            }
+            case BinaryOp::kSubtract: {
+                // %dst = sub %src, 1
+                if (lhs == src && IsOne(rhs)) {
+                    break;
+                }
+                return nullptr;
+            }
+            default:
+                return nullptr;
+        }
+
+        // 3rd instruction:
+        // store %loop_control_variable, %dst
+        const auto* store_into_loop_control_variable =
+            add_or_sub_from_loop_control_variable->next->As<Store>();
+        if (!store_into_loop_control_variable) {
+            return nullptr;
+        }
+        const auto* dst = add_or_sub_from_loop_control_variable->Result(0);
+        if (store_into_loop_control_variable->From() != dst) {
+            return nullptr;
+        }
+        if (store_into_loop_control_variable->To() != loop_control_variable->Result(0)) {
+            return nullptr;
+        }
+
+        // 4th instruction:
+        // next_iteration
+        if (!store_into_loop_control_variable->next->As<NextIteration>()) {
+            return nullptr;
+        }
+
+        return add_or_sub_from_loop_control_variable;
+    }
+
   private:
     Function* function_;
     Hashmap<const FunctionParam*, Vector<IntegerRangeInfo, 3>, 4>
@@ -160,4 +274,11 @@
     return impl_->GetLoopControlVariableFromConstantInitializer(loop);
 }
 
+const Binary* IntegerRangeAnalysis::GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(
+    const Loop* loop,
+    const Var* loop_control_variable) {
+    return impl_->GetBinaryToUpdateLoopControlVariableInContinuingBlock(loop,
+                                                                        loop_control_variable);
+}
+
 }  // namespace tint::core::ir::analysis
diff --git a/src/tint/lang/core/ir/analysis/integer_range_analysis.h b/src/tint/lang/core/ir/analysis/integer_range_analysis.h
index 010c5c8..00576c5 100644
--- a/src/tint/lang/core/ir/analysis/integer_range_analysis.h
+++ b/src/tint/lang/core/ir/analysis/integer_range_analysis.h
@@ -32,6 +32,7 @@
 #include <variant>
 
 namespace tint::core::ir {
+class Binary;
 class Function;
 class FunctionParam;
 class Loop;
@@ -92,6 +93,23 @@
     /// requirements, return nullptr otherwise.
     const Var* GetLoopControlVariableFromConstantInitializerForTest(const Loop* loop);
 
+    /// Note: This function is only for tests.
+    /// Returns the pointer of the binary operation that updates the loop control variable in the
+    /// continuing block of the given loop if the loop meets the below requirements.
+    /// - There are only 4 instructions in the loop initializer block.
+    /// - The first instruction is to load the loop control variable into a temporary variable.
+    /// - The second instruction is to add one or minus one to the temporary variable.
+    /// - The third instruction is to store the value of the temporary variable into the loop
+    ///   control variable.
+    /// - The fourth instruction is `next_iteration`.
+    /// @param loop the Loop variable to investigate
+    /// @returns the pointer of the binary operation that updates the loop control variable in the
+    /// continuing block of the given loop if the loop meets all the requirements, return nullptr
+    /// otherwise.
+    const Binary* GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(
+        const Loop* loop,
+        const Var* loop_control_variable);
+
   private:
     IntegerRangeAnalysis(const IntegerRangeAnalysis&) = delete;
     IntegerRangeAnalysis(IntegerRangeAnalysis&&) = delete;
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 96e9690..9601f60 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
@@ -585,5 +585,982 @@
     EXPECT_EQ(nullptr, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
 }
 
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_AddOne_sint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_i);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Add<i32>(b.Load(idx), 1_i);
+            b.Store(idx, binary);
+            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 0i
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:i32 = load %idx
+        %4:i32 = add %3, 1i
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_AddOne_uint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Add<u32>(b.Load(idx), 1_u);
+            b.Store(idx, binary);
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = add %3, 1u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_OneAddLoopControlVariable_sint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_i);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Add<i32>(1_i, b.Load(idx));
+            b.Store(idx, binary);
+            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 0i
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:i32 = load %idx
+        %4:i32 = add 1i, %3
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_OneAddLoopControlVariable_uint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Add<u32>(1_u, b.Load(idx));
+            b.Store(idx, binary);
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = add 1u, %3
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_MinusOne_sint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_i);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Subtract<i32>(b.Load(idx), 1_i);
+            b.Store(idx, binary);
+            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 0i
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:i32 = load %idx
+        %4:i32 = sub %3, 1i
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Success_MinusOne_uint) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Binary* binary = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            binary = b.Subtract<u32>(b.Load(idx), 1_u);
+            b.Store(idx, binary);
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = sub %3, 1u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(binary,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_TooFewInstructions) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            b.Store(idx, b.Load(idx));
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        store %idx, %3
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_TooManyInstructions) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            auto* addx = b.Add<u32>(b.Load(idx), 1_u);
+            auto* minusx = b.Subtract<u32>(addx, 1_u);
+            b.Store(idx, minusx);
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = add %3, 1u
+        %5:u32 = sub %4, 1u
+        store %idx, %5
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_FirstInstructionIsNotLoad) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            Var* idy = b.Var("idy", 1_u);
+            b.Store(idx, b.Load(idy));
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %idy:ptr<function, u32, read_write> = var 1u
+        %4:u32 = load %idy
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_NoLoadFromLoopControlVariable) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Var* idy = nullptr;
+    auto* end = b.FunctionParam("end", ty.u32());
+    auto* func = b.Function("func", ty.void_());
+    func->AppendParam(end);
+    b.Append(func->Block(), [&] {
+        idy = b.Var("idy", end);
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx = idy + 1
+            b.Store(idx, b.Add<u32>(b.Load(idy), 1_u));
+            b.NextIteration(loop);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%func = func(%end:u32):void {
+  $B1: {
+    %idy:ptr<function, u32, read_write> = var %end
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %idx:ptr<function, u32, read_write> = var 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %5:u32 = load %idy
+        %6:u32 = add %5, 1u
+        store %idx, %6
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_SecondInstructionNotABinaryOp) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx = bitcast<u32>(idx);
+            b.Store(idx, b.Bitcast<u32>(b.Load(idx)));
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = bitcast %3
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_AddTwo) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx += 2u;
+            b.Store(idx, b.Add<u32>(b.Load(idx), 2_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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = add %3, 2u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_MinusTwo) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx -= 2u
+            b.Store(idx, b.Subtract<u32>(b.Load(idx), 2_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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = sub %3, 2u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_OneMinusLoopControlVariable) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx = 1u - idx
+            b.Store(idx, b.Subtract<u32>(1_u, b.Load(idx)));
+            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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = sub 1u, %3
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_AddNonConstantValue) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* end = b.FunctionParam("end", ty.u32());
+    auto* func = b.Function("func", ty.void_());
+    func->AppendParam(end);
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx += end
+            b.Store(idx, b.Add<u32>(b.Load(idx), end));
+            b.NextIteration(loop);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%func = func(%end:u32):void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %idx:ptr<function, u32, read_write> = var 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %4:u32 = load %idx
+        %5:u32 = add %4, %end
+        store %idx, %5
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_MinusNonConstantValue) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* end = b.FunctionParam("end", ty.u32());
+    auto* func = b.Function("func", ty.void_());
+    func->AppendParam(end);
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx -= end
+            b.Store(idx, b.Subtract<u32>(b.Load(idx), end));
+            b.NextIteration(loop);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%func = func(%end:u32):void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %idx:ptr<function, u32, read_write> = var 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %4:u32 = load %idx
+        %5:u32 = sub %4, %end
+        store %idx, %5
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_NeitherAddNorMinus) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    auto* func = b.Function("func", ty.void_());
+    b.Append(func->Block(), [&] {
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idx << 1ui;
+            b.Store(idx, b.ShiftLeft<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 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %3:u32 = load %idx
+        %4:u32 = shl %3, 1u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_NoStoreFromLoopControlVariable) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Var* idy = nullptr;
+    auto* end = b.FunctionParam("end", ty.u32());
+    auto* func = b.Function("func", ty.void_());
+    func->AppendParam(end);
+    b.Append(func->Block(), [&] {
+        idy = b.Var("idy", end);
+        auto* loady = b.Load(idy);
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            b.Add<u32>(b.Load(idx), 1_u);
+            // idx = idy
+            b.Store(idx, loady);
+            b.NextIteration(loop);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%func = func(%end:u32):void {
+  $B1: {
+    %idy:ptr<function, u32, read_write> = var %end
+    %4:u32 = load %idy
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %idx:ptr<function, u32, read_write> = var 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %6:u32 = load %idx
+        %7:u32 = add %6, 1u
+        store %idx, %4
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
+TEST_F(IR_IntegerRangeAnalysisTest, AnalyzeLoopContinuing_Failure_NoStoreToLoopControlVariable) {
+    Var* idx = nullptr;
+    Loop* loop = nullptr;
+    Var* idy = nullptr;
+    auto* end = b.FunctionParam("end", ty.u32());
+    auto* func = b.Function("func", ty.void_());
+    func->AppendParam(end);
+    b.Append(func->Block(), [&] {
+        idy = b.Var("idy", end);
+        loop = b.Loop();
+        b.Append(loop->Initializer(), [&] {
+            idx = b.Var("idx", 0_u);
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+        b.Append(loop->Continuing(), [&] {
+            // idy = idx + 1
+            b.Store(idy, b.Add<u32>(b.Load(idx), 1_u));
+            b.NextIteration(loop);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%func = func(%end:u32):void {
+  $B1: {
+    %idy:ptr<function, u32, read_write> = var %end
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %idx:ptr<function, u32, read_write> = var 0u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        exit_loop  # loop_1
+      }
+      $B4: {  # continuing
+        %5:u32 = load %idx
+        %6:u32 = add %5, 1u
+        store %idy, %6
+        next_iteration  # -> $B3
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    EXPECT_EQ(Validate(mod), Success);
+
+    IntegerRangeAnalysis analysis(func);
+    EXPECT_EQ(idx, analysis.GetLoopControlVariableFromConstantInitializerForTest(loop));
+    EXPECT_EQ(nullptr,
+              analysis.GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(loop, idx));
+}
+
 }  // namespace
 }  // namespace tint::core::ir::analysis