Improve unit testing of for-loops.

An ES2-compatible for loop supports six separate rel-ops:
   < <= > >= != ==
Each rel-op, in addition to its expected usage, is also able to
represent a loop which never terminates, as well as a loop which
terminates instantly. Since SkVM unrolls these loops, we should make
sure we do it properly. We now have unit tests for all of these cases.

Change-Id: Icae04d48bc158bf8c0c98db97f76756a1a29110c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/445756
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
diff --git a/resources/sksl/runtime/LoopFloat.rts b/resources/sksl/runtime/LoopFloat.rts
index 96c7759..53f409f 100644
--- a/resources/sksl/runtime/LoopFloat.rts
+++ b/resources/sksl/runtime/LoopFloat.rts
@@ -1,3 +1,5 @@
+uniform half4 colorRed, colorGreen;
+
 // Should return 5
 float return_loop() {
     for (float i = 0; i < 10; ++i) {
@@ -26,15 +28,89 @@
     return sum;
 }
 
-// Should return ~1.725
+// Should return a value close to zero
 float float_loop() {
     float sum = 0;
     for (float i = 0.123; i < 0.6; i += 0.111) {
         sum += i;
     }
-    return sum;
+    return sum - 1.725;
+}
+
+bool loop_operator_le() {
+    // These loops are inside-out and execute zero times.
+    for (float i = 3; i <= 1; ++i) { return false; }
+    for (float i = 3; i <= 1; --i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 1; i <= 3; ++i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 1, 2, 3);
+}
+
+bool loop_operator_lt() {
+    // These loops are inside-out and execute zero times.
+    for (float i = 4; i < 1; ++i) { return false; }
+    for (float i = 4; i < 1; --i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 1; i < 4; ++i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 1, 2, 3);
+}
+
+bool loop_operator_ge() {
+    // These loops are inside-out and execute zero times.
+    for (float i = 1; i >= 3; ++i) { return false; }
+    for (float i = 1; i >= 3; --i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 3; i >= 1; --i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 3, 2, 1);
+}
+
+bool loop_operator_gt() {
+    // These loops are inside-out and execute zero times.
+    for (float i = 0; i > 3; ++i) { return false; }
+    for (float i = 0; i > 3; --i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 3; i > 0; --i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 3, 2, 1);
+}
+
+bool loop_operator_ne() {
+    // This loop executes zero times.
+    for (int i = 1; i != 1; ++i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 1; i != 4; ++i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 1, 2, 3);
+}
+
+bool loop_operator_eq() {
+    // This loops mismatches and executes zero times.
+    for (float i = 1; i == 2; ++i) { return false; }
+
+    float4 result = float4(9);
+    for (float i = 1; i == 1; ++i) {
+        result = float4(result.yzw, i);
+    }
+    return result == float4(9, 9, 9, 1);
 }
 
 half4 main(float2 xy) {
-    return half4(return_loop(), continue_loop(), break_loop(), float_loop());
+    return (return_loop() == 5 && continue_loop() == 35 &&
+            break_loop() == 15 && abs(float_loop()) < 0.025 &&
+            loop_operator_le() && loop_operator_lt() &&
+            loop_operator_ge() && loop_operator_gt() &&
+            loop_operator_eq() && loop_operator_ne()) ? colorGreen : colorRed;
 }
diff --git a/resources/sksl/runtime/LoopInt.rts b/resources/sksl/runtime/LoopInt.rts
index 9faec3d..bda39ca 100644
--- a/resources/sksl/runtime/LoopInt.rts
+++ b/resources/sksl/runtime/LoopInt.rts
@@ -1,3 +1,5 @@
+uniform half4 colorRed, colorGreen;
+
 // Should return 5
 int return_loop() {
     for (int i = 0; i < 10; ++i) {
@@ -26,6 +28,79 @@
     return sum;
 }
 
+bool loop_operator_le() {
+    // These loops are inside-out and execute zero times.
+    for (int i = 3; i <= 1; ++i) { return false; }
+    for (int i = 3; i <= 1; --i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 1; i <= 3; ++i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 1, 2, 3);
+}
+
+bool loop_operator_lt() {
+    // These loops are inside-out and execute zero times.
+    for (int i = 4; i < 1; ++i) { return false; }
+    for (int i = 4; i < 1; --i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 1; i < 4; ++i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 1, 2, 3);
+}
+
+bool loop_operator_ge() {
+    // These loops are inside-out and execute zero times.
+    for (int i = 1; i >= 3; ++i) { return false; }
+    for (int i = 1; i >= 3; --i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 3; i >= 1; --i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 3, 2, 1);
+}
+
+bool loop_operator_gt() {
+    // These loops are inside-out and execute zero times.
+    for (int i = 0; i > 3; ++i) { return false; }
+    for (int i = 0; i > 3; --i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 3; i > 0; --i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 3, 2, 1);
+}
+
+bool loop_operator_ne() {
+    // This loop executes zero times.
+    for (int i = 1; i != 1; ++i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 1; i != 4; ++i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 1, 2, 3);
+}
+
+bool loop_operator_eq() {
+    // This loop executes zero times.
+    for (int i = 1; i == 2; ++i) { return false; }
+
+    int4 result = int4(9);
+    for (int i = 1; i == 1; ++i) {
+        result = int4(result.yzw, i);
+    }
+    return result == int4(9, 9, 9, 1);
+}
+
 half4 main(float2 xy) {
-    return half4(return_loop(), continue_loop(), break_loop(), 1);
+    return (return_loop() == 5 && continue_loop() == 35 && break_loop() == 15 &&
+            loop_operator_le() && loop_operator_lt() &&
+            loop_operator_ge() && loop_operator_gt() &&
+            loop_operator_eq() && loop_operator_ne()) ? colorGreen : colorRed;
 }
diff --git a/resources/sksl/runtime_errors/LoopStructureErrors.rts b/resources/sksl/runtime_errors/LoopStructureErrors.rts
index 48a5c60..dd288c9 100644
--- a/resources/sksl/runtime_errors/LoopStructureErrors.rts
+++ b/resources/sksl/runtime_errors/LoopStructureErrors.rts
@@ -1,8 +1,10 @@
-// Expect 5 errors
+// Expect 15 errors
 
-void loop_length_ok() { for (int i = 0; i < 128; i++) {} }  // LEGAL: See kMaxUnrollableLoopLength
-void loop_too_long()  { for (int i = 0; i < 129; i++) {} }
-void infinite_loop()  { for (int i = 0; i < 1; i += 0) {} }
+void loop_length_128()    { for (int i = 0; i < 128; i++) {} } // OK, under kMaxUnrollableLoopLength
+void loop_length_129()    { for (int i = 0; i < 129; i++) {} }
+void loop_length_99999()  { for (int i = 0; i < 99999; i++) {} }
+void loop_length_100000() { for (int i = 0; i < 100000; i++) {} }
+void infinite_loop()      { for (int i = 0; i < 1; i += 0) {} }
 
 void set(out int x)   { x = 1; }
 void inc(inout int x) { x++; }
@@ -10,3 +12,12 @@
 void index_modified()    { for (int i = 0; i < 2; i++) { i++; } }
 void index_out_param()   { for (int i = 0; i < 1; i++) { set(i); } }
 void index_inout_param() { for (int i = 0; i < 1; i++) { inc(i); } }
+
+void infinite_loop_le()   { for (int i = 0; i <= 3; --i)  {} }
+void infinite_loop_lt()   { for (int i = 0; i <  4; --i)  {} }
+void infinite_loop_ge()   { for (int i = 3; i >= 0; ++i)  {} }
+void infinite_loop_gt()   { for (int i = 3; i > -1; ++i)  {} }
+void infinite_loop_eq1()  { for (int i = 0; i == 0; i-=0) {} }
+void infinite_loop_eq2()  { for (int i = 1; i == 1; i+=0) {} }
+void infinite_loop_ne1()  { for (int i = 0; i != 4; i--)  {} }
+void infinite_loop_ne2()  { for (int i = 0; i != 4; i+=3) {} }
diff --git a/tests/SkSLTest.cpp b/tests/SkSLTest.cpp
index 34a07f2..e205c2f 100644
--- a/tests/SkSLTest.cpp
+++ b/tests/SkSLTest.cpp
@@ -230,6 +230,8 @@
 SKSL_TEST_ES3(SkSLIntrinsicUintBitsToFloat,    "intrinsics/UintBitsToFloat.sksl")
 
 SKSL_TEST_ES3(SkSLArrayNarrowingConversions,   "runtime/ArrayNarrowingConversions.rts")
+SKSL_TEST_ES3(SkSLLoopFloat,                   "runtime/LoopFloat.rts")
+SKSL_TEST_ES3(SkSLLoopInt,                     "runtime/LoopInt.rts")
 
 SKSL_TEST_ES3(SkSLArrayComparison,             "shared/ArrayComparison.sksl")
 SKSL_TEST_ES3(SkSLArrayConstructors,           "shared/ArrayConstructors.sksl")
diff --git a/tests/sksl/runtime/LoopFloat.skvm b/tests/sksl/runtime/LoopFloat.skvm
index 85795b9..17da1ad 100644
--- a/tests/sksl/runtime/LoopFloat.skvm
+++ b/tests/sksl/runtime/LoopFloat.skvm
@@ -1,8 +1,8 @@
 4 registers, 8 instructions:
-0	r0 = splat 40A00000 (5)
-1	r1 = splat 420C0000 (35)
-2	r2 = splat 41700000 (15)
-3	r3 = splat 3FDCCCCD (1.725)
+0	r0 = uniform32 ptr0 14
+1	r1 = uniform32 ptr0 18
+2	r2 = uniform32 ptr0 1C
+3	r3 = uniform32 ptr0 20
 loop:
 4	    store32 ptr1 r0
 5	    store32 ptr2 r1
diff --git a/tests/sksl/runtime/LoopFloat.stage b/tests/sksl/runtime/LoopFloat.stage
index 9793dbe..c2e47b0 100644
--- a/tests/sksl/runtime/LoopFloat.stage
+++ b/tests/sksl/runtime/LoopFloat.stage
@@ -1,3 +1,5 @@
+uniform half4 colorRed;
+uniform half4 colorGreen;
 float return_loop_0()
 {
 	for (float i = 0.0;i < 10.0; ++i) 
@@ -9,30 +11,136 @@
 	}
 	return 0.0;
 }
-half4 main(float2 xy)
+float continue_loop_0()
 {
-	float _0_sum = 0.0;
-	for (float _1_i = 0.0;_1_i < 10.0; ++_1_i) 
+	float sum = 0.0;
+	for (float i = 0.0;i < 10.0; ++i) 
 	{
-		if (_1_i < 5.0) 
+		if (i < 5.0) 
 		{
 			continue;
 		}
-		_0_sum += _1_i;
+		sum += i;
 	}
-	float _2_sum = 0.0;
-	for (float _3_i = 0.0;_3_i < 10.0; ++_3_i) 
+	return sum;
+}
+float break_loop_0()
+{
+	float sum = 0.0;
+	for (float i = 0.0;i < 10.0; ++i) 
 	{
-		if (_3_i > 5.0) 
+		if (i > 5.0) 
 		{
 			break;
 		}
-		_2_sum += _3_i;
+		sum += i;
 	}
-	float _4_sum = 0.0;
-	for (float _5_i = 0.12300000339746475;_5_i < 0.60000002384185791; _5_i += 0.11100000143051147) 
+	return sum;
+}
+float float_loop_0()
+{
+	float sum = 0.0;
+	for (float i = 0.12300000339746475;i < 0.60000002384185791; i += 0.11100000143051147) 
 	{
-		_4_sum += _5_i;
+		sum += i;
 	}
-	return half4(half4(half(return_loop_0()), half(_0_sum), half(_2_sum), half(_4_sum)));
+	return sum - 1.7250000238418579;
+}
+bool loop_operator_le_0()
+{
+	for (float i = 3.0;i <= 1.0; ++i) 
+	{
+		return false;
+	}
+	for (float i = 3.0;i <= 1.0; --i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 1.0;i <= 3.0; ++i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 1.0, 2.0, 3.0);
+}
+bool loop_operator_lt_0()
+{
+	for (float i = 4.0;i < 1.0; ++i) 
+	{
+		return false;
+	}
+	for (float i = 4.0;i < 1.0; --i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 1.0;i < 4.0; ++i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 1.0, 2.0, 3.0);
+}
+bool loop_operator_ge_0()
+{
+	for (float i = 1.0;i >= 3.0; ++i) 
+	{
+		return false;
+	}
+	for (float i = 1.0;i >= 3.0; --i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 3.0;i >= 1.0; --i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 3.0, 2.0, 1.0);
+}
+bool loop_operator_gt_0()
+{
+	for (float i = 0.0;i > 3.0; ++i) 
+	{
+		return false;
+	}
+	for (float i = 0.0;i > 3.0; --i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 3.0;i > 0.0; --i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 3.0, 2.0, 1.0);
+}
+bool loop_operator_ne_0()
+{
+	for (int i = 1;i != 1; ++i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 1.0;i != 4.0; ++i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 1.0, 2.0, 3.0);
+}
+bool loop_operator_eq_0()
+{
+	for (float i = 1.0;i == 2.0; ++i) 
+	{
+		return false;
+	}
+	float4 result = float4(9.0);
+	for (float i = 1.0;i == 1.0; ++i) 
+	{
+		result = float4(result.yzw, i);
+	}
+	return result == float4(9.0, 9.0, 9.0, 1.0);
+}
+half4 main(float2 xy)
+{
+	return half4(((((((((return_loop_0() == 5.0 && continue_loop_0() == 35.0) && break_loop_0() == 15.0) && abs(float_loop_0()) < 0.02500000037252903) && loop_operator_le_0()) && loop_operator_lt_0()) && loop_operator_ge_0()) && loop_operator_gt_0()) && loop_operator_eq_0()) && loop_operator_ne_0() ? colorGreen : colorRed);
 }
diff --git a/tests/sksl/runtime/LoopInt.skvm b/tests/sksl/runtime/LoopInt.skvm
index b1c3c4d..17da1ad 100644
--- a/tests/sksl/runtime/LoopInt.skvm
+++ b/tests/sksl/runtime/LoopInt.skvm
@@ -1,8 +1,8 @@
 4 registers, 8 instructions:
-0	r0 = splat 40A00000 (5)
-1	r1 = splat 420C0000 (35)
-2	r2 = splat 41700000 (15)
-3	r3 = splat 3F800000 (1)
+0	r0 = uniform32 ptr0 14
+1	r1 = uniform32 ptr0 18
+2	r2 = uniform32 ptr0 1C
+3	r3 = uniform32 ptr0 20
 loop:
 4	    store32 ptr1 r0
 5	    store32 ptr2 r1
diff --git a/tests/sksl/runtime/LoopInt.stage b/tests/sksl/runtime/LoopInt.stage
index fe12dd6..bfbffb4 100644
--- a/tests/sksl/runtime/LoopInt.stage
+++ b/tests/sksl/runtime/LoopInt.stage
@@ -1,3 +1,5 @@
+uniform half4 colorRed;
+uniform half4 colorGreen;
 int return_loop_0()
 {
 	for (int i = 0;i < 10; ++i) 
@@ -9,25 +11,127 @@
 	}
 	return 0;
 }
-half4 main(float2 xy)
+int continue_loop_0()
 {
-	int _0_sum = 0;
-	for (int _1_i = 0;_1_i < 10; ++_1_i) 
+	int sum = 0;
+	for (int i = 0;i < 10; ++i) 
 	{
-		if (_1_i < 5) 
+		if (i < 5) 
 		{
 			continue;
 		}
-		_0_sum += _1_i;
+		sum += i;
 	}
-	int _2_sum = 0;
-	for (int _3_i = 0;_3_i < 10; ++_3_i) 
+	return sum;
+}
+int break_loop_0()
+{
+	int sum = 0;
+	for (int i = 0;i < 10; ++i) 
 	{
-		if (_3_i > 5) 
+		if (i > 5) 
 		{
 			break;
 		}
-		_2_sum += _3_i;
+		sum += i;
 	}
-	return half4(half4(half(return_loop_0()), half(_0_sum), half(_2_sum), 1.0));
+	return sum;
+}
+bool loop_operator_le_0()
+{
+	for (int i = 3;i <= 1; ++i) 
+	{
+		return false;
+	}
+	for (int i = 3;i <= 1; --i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 1;i <= 3; ++i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 1, 2, 3);
+}
+bool loop_operator_lt_0()
+{
+	for (int i = 4;i < 1; ++i) 
+	{
+		return false;
+	}
+	for (int i = 4;i < 1; --i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 1;i < 4; ++i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 1, 2, 3);
+}
+bool loop_operator_ge_0()
+{
+	for (int i = 1;i >= 3; ++i) 
+	{
+		return false;
+	}
+	for (int i = 1;i >= 3; --i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 3;i >= 1; --i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 3, 2, 1);
+}
+bool loop_operator_gt_0()
+{
+	for (int i = 0;i > 3; ++i) 
+	{
+		return false;
+	}
+	for (int i = 0;i > 3; --i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 3;i > 0; --i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 3, 2, 1);
+}
+bool loop_operator_ne_0()
+{
+	for (int i = 1;i != 1; ++i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 1;i != 4; ++i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 1, 2, 3);
+}
+bool loop_operator_eq_0()
+{
+	for (int i = 1;i == 2; ++i) 
+	{
+		return false;
+	}
+	int4 result = int4(9);
+	for (int i = 1;i == 1; ++i) 
+	{
+		result = int4(result.yzw, i);
+	}
+	return result == int4(9, 9, 9, 1);
+}
+half4 main(float2 xy)
+{
+	return half4((((((((return_loop_0() == 5 && continue_loop_0() == 35) && break_loop_0() == 15) && loop_operator_le_0()) && loop_operator_lt_0()) && loop_operator_ge_0()) && loop_operator_gt_0()) && loop_operator_eq_0()) && loop_operator_ne_0() ? colorGreen : colorRed);
 }
diff --git a/tests/sksl/runtime_errors/LoopStructureErrors.skvm b/tests/sksl/runtime_errors/LoopStructureErrors.skvm
index 1c169dc..abd7c5f 100644
--- a/tests/sksl/runtime_errors/LoopStructureErrors.skvm
+++ b/tests/sksl/runtime_errors/LoopStructureErrors.skvm
@@ -1,8 +1,18 @@
 ### Compilation failed:
 
 error: 4: loop must guarantee termination in fewer iterations
-error: 5: invalid loop expression
-error: 10: loop index must not be modified within body of the loop
-error: 11: loop index must not be modified within body of the loop
+error: 5: loop must guarantee termination in fewer iterations
+error: 6: loop must guarantee termination in fewer iterations
+error: 7: invalid loop expression
 error: 12: loop index must not be modified within body of the loop
-5 errors
+error: 13: loop index must not be modified within body of the loop
+error: 14: loop index must not be modified within body of the loop
+error: 16: loop must guarantee termination in fewer iterations
+error: 17: loop must guarantee termination in fewer iterations
+error: 18: loop must guarantee termination in fewer iterations
+error: 19: loop must guarantee termination in fewer iterations
+error: 20: invalid loop expression
+error: 21: invalid loop expression
+error: 22: loop must guarantee termination in fewer iterations
+error: 23: loop must guarantee termination in fewer iterations
+15 errors