Enforce an upper bound on Runtime Effect program size.

The fuzzer is currently learning to make unboundedly-large programs by
nesting medium-size loops repeatedly. SkVM doesn't have a mechanism to
limit the ensuing explosion of code and ends up making unreasonably deep
stacks and/or unreasonably large programs.

SkSL now enforces an upper bound of approximately 100,000 IR nodes on a
fully-flattened, fully-inlined strict-ES2 program. The limit is picked
out of thin air, but this should be enough to prevent SkVM from going
haywire while still being large enough to handle any reasonable program.
We can definitely tune this value if we find that it is too large
(admitting dangerous code) or too small (rejecting good code).

Change-Id: I11735636175721fbc79460b4e194d8e4b42dc47d
Bug: skia:12396, oss-fuzz:37827, oss-fuzz:37837
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/444358
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index 6f84056..2ede872 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -532,6 +532,11 @@
   "/sksl/runtime/ConversionConstructors.rts",
   "/sksl/runtime/GLSLTypeNames.rts",
   "/sksl/runtime/GlobalVariables.rts",
+  "/sksl/runtime/LargeProgram_FlatLoop.rts",
+  "/sksl/runtime/LargeProgram_Functions.rts",
+  "/sksl/runtime/LargeProgram_NestedLoops.rts",
+  "/sksl/runtime/LargeProgram_SplitLoops.rts",
+  "/sksl/runtime/LargeProgram_ZeroIterFor.rts",
   "/sksl/runtime/LoopInt.rts",
   "/sksl/runtime/LoopFloat.rts",
   "/sksl/runtime/PrecisionQualifiers.rts",
@@ -562,6 +567,10 @@
   "/sksl/runtime_errors/LoopInitializerErrors.rts",
   "/sksl/runtime_errors/LoopStructureErrors.rts",
   "/sksl/runtime_errors/Ossfuzz36655.rts",
+  "/sksl/runtime_errors/ProgramTooLarge_FlatLoop.rts",
+  "/sksl/runtime_errors/ProgramTooLarge_Functions.rts",
+  "/sksl/runtime_errors/ProgramTooLarge_NestedLoops.rts",
+  "/sksl/runtime_errors/ProgramTooLarge_SplitLoops.rts",
   "/sksl/runtime_errors/UnsupportedTypeFragmentProcessor.rts",
   "/sksl/runtime_errors/UnsupportedTypeSampler.rts",
   "/sksl/runtime_errors/UnsupportedTypeTexture.rts",
diff --git a/resources/sksl/runtime/LargeProgram_FlatLoop.rts b/resources/sksl/runtime/LargeProgram_FlatLoop.rts
new file mode 100644
index 0000000..db3b089
--- /dev/null
+++ b/resources/sksl/runtime/LargeProgram_FlatLoop.rts
@@ -0,0 +1,61 @@
+half4 main(float2 xy) {
+    int i;
+
+    for (int a=0; a<100; ++a) {
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 1000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 2000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 3000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 4000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 5000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 6000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 7000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 8000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 9000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 10000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 11000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 12000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 13000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 14000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 15000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 16000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 17000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 18000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 19000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 20000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 21000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 22000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 23000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 24000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 25000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 26000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 27000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 28000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 29000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 30000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 31000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 32000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 33000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 34000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 35000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 36000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 37000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 38000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 39000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 40000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 41000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 42000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 43000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 44000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 45000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 46000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 47000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 48000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 49000
+    }
+
+    return half4(0);
+}
diff --git a/resources/sksl/runtime/LargeProgram_Functions.rts b/resources/sksl/runtime/LargeProgram_Functions.rts
new file mode 100644
index 0000000..95963e9
--- /dev/null
+++ b/resources/sksl/runtime/LargeProgram_Functions.rts
@@ -0,0 +1,10 @@
+void d(inout int i) { ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; }           // 10000
+void c(inout int i) { d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); } // 1000
+void b(inout int i) { c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); } // 100
+void a(inout int i) { b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); } // 10
+
+half4 main(float2 xy) {
+    int i = 0;
+    a(i);
+    return half4(0);
+}
diff --git a/resources/sksl/runtime/LargeProgram_NestedLoops.rts b/resources/sksl/runtime/LargeProgram_NestedLoops.rts
new file mode 100644
index 0000000..313817c
--- /dev/null
+++ b/resources/sksl/runtime/LargeProgram_NestedLoops.rts
@@ -0,0 +1,12 @@
+half4 main(float2 xy) {
+    int i;
+
+    for (int a=0; a<10; ++a) { // 10
+    for (int b=0; b<10; ++b) { // 100
+    for (int c=0; c<10; ++c) { // 1000
+    for (int d=0; d<10; ++d) { // 10000
+        ++i;
+    }}}}
+
+    return half4(0);
+}
diff --git a/resources/sksl/runtime/LargeProgram_SplitLoops.rts b/resources/sksl/runtime/LargeProgram_SplitLoops.rts
new file mode 100644
index 0000000..b48e6f4
--- /dev/null
+++ b/resources/sksl/runtime/LargeProgram_SplitLoops.rts
@@ -0,0 +1,10 @@
+void d(inout int i) { for (int x=0; x<10; ++x) ++i;  } // 10000
+void c(inout int i) { for (int x=0; x<10; ++x) d(i); } // 1000
+void b(inout int i) { for (int x=0; x<10; ++x) c(i); } // 100
+void a(inout int i) { for (int x=0; x<10; ++x) b(i); } // 10
+
+half4 main(float2 xy) {
+    int i = 0;
+    a(i);
+    return half4(0);
+}
diff --git a/resources/sksl/runtime/LargeProgram_ZeroIterFor.rts b/resources/sksl/runtime/LargeProgram_ZeroIterFor.rts
new file mode 100644
index 0000000..1204bda
--- /dev/null
+++ b/resources/sksl/runtime/LargeProgram_ZeroIterFor.rts
@@ -0,0 +1,14 @@
+half4 main(float2 xy) {
+    int i = 0;
+
+    for (int a=0; a<10;  ++a) { // 10
+    for (int b=0; b<0;   ++b) { // 0
+    for (int c=0; c<100; ++c) { // 0
+    for (int d=0; d<100; ++d) { // 0
+    for (int e=0; e<100; ++e) { // 0
+    for (int f=0; f<100; ++f) { // 0
+        ++i;
+    }}}}}}
+
+    return half4(i);
+}
diff --git a/resources/sksl/runtime_errors/ProgramTooLarge_FlatLoop.rts b/resources/sksl/runtime_errors/ProgramTooLarge_FlatLoop.rts
new file mode 100644
index 0000000..66936e2
--- /dev/null
+++ b/resources/sksl/runtime_errors/ProgramTooLarge_FlatLoop.rts
@@ -0,0 +1,117 @@
+half4 main(float2 xy) {
+    int i;
+
+    for (int a=0; a<100; ++a) {
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 1000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 2000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 3000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 4000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 5000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 6000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 7000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 8000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 9000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 10000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 11000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 12000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 13000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 14000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 15000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 16000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 17000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 18000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 19000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 20000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 21000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 22000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 23000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 24000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 25000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 26000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 27000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 28000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 29000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 30000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 31000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 32000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 33000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 34000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 35000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 36000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 37000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 38000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 39000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 40000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 41000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 42000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 43000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 44000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 45000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 46000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 47000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 48000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 49000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 50000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 51000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 52000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 53000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 54000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 55000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 56000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 57000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 58000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 59000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 60000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 61000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 62000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 63000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 64000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 65000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 66000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 67000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 68000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 69000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 70000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 71000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 72000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 73000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 74000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 75000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 76000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 77000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 78000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 79000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 80000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 81000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 82000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 83000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 84000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 85000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 86000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 87000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 88000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 89000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 90000
+
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 91000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 92000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 93000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 94000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 95000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 96000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 97000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 98000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 99000
+        ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; // 100000
+    }
+
+    return half4(0);
+}
diff --git a/resources/sksl/runtime_errors/ProgramTooLarge_Functions.rts b/resources/sksl/runtime_errors/ProgramTooLarge_Functions.rts
new file mode 100644
index 0000000..4d20c4e
--- /dev/null
+++ b/resources/sksl/runtime_errors/ProgramTooLarge_Functions.rts
@@ -0,0 +1,11 @@
+void e(inout int i) { ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; ++i; }           // 100000
+void d(inout int i) { e(i); e(i); e(i); e(i); e(i); e(i); e(i); e(i); e(i); e(i); } // 10000
+void c(inout int i) { d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); d(i); } // 1000
+void b(inout int i) { c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); c(i); } // 100
+void a(inout int i) { b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); b(i); } // 10
+
+half4 main(float2 xy) {
+    int i = 0;
+    a(i);
+    return half4(0);
+}
diff --git a/resources/sksl/runtime_errors/ProgramTooLarge_NestedLoops.rts b/resources/sksl/runtime_errors/ProgramTooLarge_NestedLoops.rts
new file mode 100644
index 0000000..d39ed54
--- /dev/null
+++ b/resources/sksl/runtime_errors/ProgramTooLarge_NestedLoops.rts
@@ -0,0 +1,13 @@
+half4 main(float2 xy) {
+    int i;
+
+    for (int a=0; a<10; ++a) { // 10
+    for (int b=0; b<10; ++b) { // 100
+    for (int c=0; c<10; ++c) { // 1000
+    for (int d=0; d<10; ++d) { // 10000
+    for (int e=0; e<10; ++e) { // 100000
+        ++i;
+    }}}}}
+
+    return half4(0);
+}
diff --git a/resources/sksl/runtime_errors/ProgramTooLarge_SplitLoops.rts b/resources/sksl/runtime_errors/ProgramTooLarge_SplitLoops.rts
new file mode 100644
index 0000000..73dcf06
--- /dev/null
+++ b/resources/sksl/runtime_errors/ProgramTooLarge_SplitLoops.rts
@@ -0,0 +1,11 @@
+void e(inout int i) { for (int x=0; x<10; ++x) ++i;  } // 100000
+void d(inout int i) { for (int x=0; x<10; ++x) e(i); } // 10000
+void c(inout int i) { for (int x=0; x<10; ++x) d(i); } // 1000
+void b(inout int i) { for (int x=0; x<10; ++x) c(i); } // 100
+void a(inout int i) { for (int x=0; x<10; ++x) b(i); } // 10
+
+half4 main(float2 xy) {
+    int i = 0;
+    a(i);
+    return half4(0);
+}
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index c5db6ad..63ddc93 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -12,6 +12,7 @@
 #include "include/private/SkSLSampleUsage.h"
 #include "include/private/SkSLStatement.h"
 #include "include/sksl/SkSLErrorReporter.h"
+#include "src/core/SkSafeMath.h"
 #include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLProgram.h"
@@ -722,6 +723,148 @@
     return false;
 }
 
+bool Analysis::CheckProgramUnrolledSize(const Program& program) {
+    // We check the size of strict-ES2 programs since SkVM will completely unroll them.
+    // Note that we *cannot* safely check the program size of non-ES2 code at this time, as it is
+    // allowed to do things we can't measure (e.g. the program can contain a recursive cycle). We
+    // could, at best, compute a lower bound.
+    const Context& context = *program.fContext;
+    SkASSERT(context.fConfig->strictES2Mode());
+
+    // If we decide that expressions are cheaper than statements, or that certain statements are
+    // more expensive than others, etc., we can always tweak these ratios as needed. A very rough
+    // ballpark estimate is currently good enough for our purposes.
+    static constexpr int kExpressionCost = 1;
+    static constexpr int kStatementCost = 1;
+    static constexpr int kUnknownCost = -1;
+    static constexpr int kProgramSizeLimit = 100000;
+
+    class ProgramSizeVisitor : public ProgramVisitor {
+    public:
+        ProgramSizeVisitor() {}
+
+        using ProgramVisitor::visitProgramElement;
+
+        int programSize() const {
+            return fProgramSize;
+        }
+
+        bool visitStatement(const Statement& stmt) override {
+            switch (stmt.kind()) {
+                case Statement::Kind::kFor: {
+                    // We count a for-loop's unrolled size here. We expect that the init statement
+                    // will be emitted once, and the next-expr and statement will be repeated in the
+                    // output for every iteration of the loop. The test-expr is optimized away
+                    // during the unroll and is not counted at all.
+                    const ForStatement& forStmt = stmt.as<ForStatement>();
+                    bool result = INHERITED::visitStatement(*forStmt.initializer());
+
+                    int originalUnrollFactor = fUnrollFactor;
+
+                    if (const LoopUnrollInfo* unrollInfo = forStmt.unrollInfo()) {
+                        fUnrollFactor = SkSafeMath::Mul(fUnrollFactor, unrollInfo->fCount);
+                    } else {
+                        SkDEBUGFAIL("for-loops should always have unroll info in an ES2 program");
+                    }
+
+                    result = INHERITED::visitExpression(*forStmt.next()) ||
+                             INHERITED::visitStatement(*forStmt.statement()) || result;
+
+                    fUnrollFactor = originalUnrollFactor;
+                    return result;
+                }
+
+                case Statement::Kind::kExpression:
+                    // The cost of an expression-statement is counted in visitExpression. It would
+                    // be double-dipping to count it here too.
+                    break;
+
+                case Statement::Kind::kInlineMarker:
+                case Statement::Kind::kNop:
+                case Statement::Kind::kVarDeclaration:
+                    // These statements don't directly consume any space in a compiled program.
+                    break;
+
+                case Statement::Kind::kDo:
+                case Statement::Kind::kSwitch:
+                case Statement::Kind::kSwitchCase:
+                    SkDEBUGFAIL("encountered a statement that shouldn't exist in an ES2 program");
+                    break;
+
+                default:
+                    fProgramSize += fUnrollFactor * kStatementCost;
+                    break;
+            }
+
+            return INHERITED::visitStatement(stmt);
+        }
+
+        bool visitExpression(const Expression& expr) override {
+            // Other than function calls, all expressions are assumed to have a fixed unit cost.
+            int expressionCost = kExpressionCost;
+
+            if (expr.is<FunctionCall>()) {
+                // Calculate the cost of this function call and cache it.
+                const FunctionCall& call = expr.as<FunctionCall>();
+                const FunctionDeclaration* decl = &call.function();
+
+                if (decl->definition() && !decl->isIntrinsic()) {
+                    auto [iter, wasInserted] = fFunctionCostMap.insert({decl, kUnknownCost});
+                    if (wasInserted) {
+                        // Calculate the cost of the called function in isolation.
+                        int originalProgramSize = fProgramSize;
+                        int originalUnrollFactor = fUnrollFactor;
+
+                        fProgramSize = 0;
+                        fUnrollFactor = 1;
+                        this->visitProgramElement(*decl->definition());
+                        iter->second = expressionCost = fProgramSize;
+
+                        fProgramSize = originalProgramSize;
+                        fUnrollFactor = originalUnrollFactor;
+                    } else {
+                        if (iter->second != kUnknownCost) {
+                            expressionCost = iter->second;
+                        } else {
+                            // The function is present in the map but doesn't have a cost assigned
+                            // yet. That indicates that we found a cycle. This should've caused the
+                            // program to be rejected by Analysis::DetectStaticRecursion during
+                            // IRGenerator::finish.
+                            SkDEBUGFAIL("cycle detected in CheckProgramUnrolledSize");
+                        }
+                    }
+                }
+            }
+
+            fProgramSize += fUnrollFactor * expressionCost;
+            return INHERITED::visitExpression(expr);
+        }
+
+    private:
+        using INHERITED = ProgramVisitor;
+
+        int fProgramSize = 0;
+        int fUnrollFactor = 1;
+        std::unordered_map<const FunctionDeclaration*, int> fFunctionCostMap;
+    };
+
+    ProgramSizeVisitor visitor;
+    for (const std::unique_ptr<ProgramElement>& element : program.ownedElements()) {
+        if (element->is<FunctionDefinition>() &&
+            element->as<FunctionDefinition>().declaration().isMain()) {
+            // Determine the size of main(). Functions not referenced by main() don't cost anything.
+            visitor.visitProgramElement(*element);
+            break;
+        }
+    }
+
+    if (visitor.programSize() > kProgramSizeLimit) {
+        context.fErrors->error(/*offset=*/-1, "program is too large");
+        return false;
+    }
+    return true;
+}
+
 bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) {
     // A variable declaration can create either a lone VarDeclaration or an unscoped Block
     // containing multiple VarDeclaration statements. We need to detect either case.
diff --git a/src/sksl/SkSLAnalysis.h b/src/sksl/SkSLAnalysis.h
index 30c115c..2c9d188 100644
--- a/src/sksl/SkSLAnalysis.h
+++ b/src/sksl/SkSLAnalysis.h
@@ -54,11 +54,18 @@
 
     static bool CallsSampleOutsideMain(const Program& program);
 
-    /*
+    /**
      * Does the function call graph of the program include any cycles? If so, emits an error.
      */
     static bool DetectStaticRecursion(SkSpan<std::unique_ptr<ProgramElement>> programElements,
                                       ErrorReporter& errors);
+    /**
+     * Computes the size of the program in a completely flattened state--loops fully unrolled,
+     * function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is
+     * intended to prevent absurdly large programs from overwhemling SkVM. Only strict-ES2 mode is
+     * supported; complex control flow is not SkVM-compatible (and this becomes the halting problem)
+     */
+    static bool CheckProgramUnrolledSize(const Program& program);
 
     /**
      * Detect an orphaned variable declaration outside of a scope, e.g. if (true) int a;. Returns
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index f514d11..ba05260 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -11,7 +11,6 @@
 #include <unordered_set>
 
 #include "include/sksl/DSLCore.h"
-#include "src/core/SkScopeExit.h"
 #include "src/core/SkTraceEvent.h"
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLConstantFolder.h"
@@ -823,6 +822,7 @@
 
     if (fContext->fConfig->strictES2Mode()) {
         Analysis::DetectStaticRecursion(SkMakeSpan(program.ownedElements()), this->errorReporter());
+        Analysis::CheckProgramUnrolledSize(program);
     }
 
     return this->errorCount() == 0;
diff --git a/tests/sksl/runtime/LargeProgram_FlatLoop.skvm b/tests/sksl/runtime/LargeProgram_FlatLoop.skvm
new file mode 100644
index 0000000..68f2b9d
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_FlatLoop.skvm
@@ -0,0 +1,7 @@
+1 registers, 5 instructions:
+0	r0 = splat 0 (0)
+loop:
+1	    store32 ptr1 r0
+2	    store32 ptr2 r0
+3	    store32 ptr3 r0
+4	    store32 ptr4 r0
diff --git a/tests/sksl/runtime/LargeProgram_FlatLoop.stage b/tests/sksl/runtime/LargeProgram_FlatLoop.stage
new file mode 100644
index 0000000..e0d9503
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_FlatLoop.stage
@@ -0,0 +1,498 @@
+half4 main(float2 xy)
+{
+	int i;
+	for (int a = 0;a < 100; ++a) 
+	{
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+		++i;
+	}
+	return half4(half4(0.0));
+}
diff --git a/tests/sksl/runtime/LargeProgram_Functions.skvm b/tests/sksl/runtime/LargeProgram_Functions.skvm
new file mode 100644
index 0000000..68f2b9d
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_Functions.skvm
@@ -0,0 +1,7 @@
+1 registers, 5 instructions:
+0	r0 = splat 0 (0)
+loop:
+1	    store32 ptr1 r0
+2	    store32 ptr2 r0
+3	    store32 ptr3 r0
+4	    store32 ptr4 r0
diff --git a/tests/sksl/runtime/LargeProgram_Functions.stage b/tests/sksl/runtime/LargeProgram_Functions.stage
new file mode 100644
index 0000000..1d1345c
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_Functions.stage
@@ -0,0 +1,58 @@
+void d_0(inout int i)
+{
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+	++i;
+}
+void c_0(inout int i)
+{
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+	d_0(i);
+}
+void b_0(inout int i)
+{
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+	c_0(i);
+}
+void a_0(inout int i)
+{
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+	b_0(i);
+}
+half4 main(float2 xy)
+{
+	int i = 0;
+	a_0(i);
+	return half4(half4(0.0));
+}
diff --git a/tests/sksl/runtime/LargeProgram_NestedLoops.skvm b/tests/sksl/runtime/LargeProgram_NestedLoops.skvm
new file mode 100644
index 0000000..68f2b9d
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_NestedLoops.skvm
@@ -0,0 +1,7 @@
+1 registers, 5 instructions:
+0	r0 = splat 0 (0)
+loop:
+1	    store32 ptr1 r0
+2	    store32 ptr2 r0
+3	    store32 ptr3 r0
+4	    store32 ptr4 r0
diff --git a/tests/sksl/runtime/LargeProgram_NestedLoops.stage b/tests/sksl/runtime/LargeProgram_NestedLoops.stage
new file mode 100644
index 0000000..f665579
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_NestedLoops.stage
@@ -0,0 +1,18 @@
+half4 main(float2 xy)
+{
+	int i;
+	for (int a = 0;a < 10; ++a) 
+	{
+		for (int b = 0;b < 10; ++b) 
+		{
+			for (int c = 0;c < 10; ++c) 
+			{
+				for (int d = 0;d < 10; ++d) 
+				{
+					++i;
+				}
+			}
+		}
+	}
+	return half4(half4(0.0));
+}
diff --git a/tests/sksl/runtime/LargeProgram_SplitLoops.skvm b/tests/sksl/runtime/LargeProgram_SplitLoops.skvm
new file mode 100644
index 0000000..68f2b9d
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_SplitLoops.skvm
@@ -0,0 +1,7 @@
+1 registers, 5 instructions:
+0	r0 = splat 0 (0)
+loop:
+1	    store32 ptr1 r0
+2	    store32 ptr2 r0
+3	    store32 ptr3 r0
+4	    store32 ptr4 r0
diff --git a/tests/sksl/runtime/LargeProgram_SplitLoops.stage b/tests/sksl/runtime/LargeProgram_SplitLoops.stage
new file mode 100644
index 0000000..987dba0
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_SplitLoops.stage
@@ -0,0 +1,22 @@
+void d_0(inout int i)
+{
+	for (int x = 0;x < 10; ++x) ++i;
+}
+void c_0(inout int i)
+{
+	for (int x = 0;x < 10; ++x) d_0(i);
+}
+void b_0(inout int i)
+{
+	for (int x = 0;x < 10; ++x) c_0(i);
+}
+void a_0(inout int i)
+{
+	for (int x = 0;x < 10; ++x) b_0(i);
+}
+half4 main(float2 xy)
+{
+	int i = 0;
+	a_0(i);
+	return half4(half4(0.0));
+}
diff --git a/tests/sksl/runtime/LargeProgram_ZeroIterFor.skvm b/tests/sksl/runtime/LargeProgram_ZeroIterFor.skvm
new file mode 100644
index 0000000..68f2b9d
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_ZeroIterFor.skvm
@@ -0,0 +1,7 @@
+1 registers, 5 instructions:
+0	r0 = splat 0 (0)
+loop:
+1	    store32 ptr1 r0
+2	    store32 ptr2 r0
+3	    store32 ptr3 r0
+4	    store32 ptr4 r0
diff --git a/tests/sksl/runtime/LargeProgram_ZeroIterFor.stage b/tests/sksl/runtime/LargeProgram_ZeroIterFor.stage
new file mode 100644
index 0000000..64b1855
--- /dev/null
+++ b/tests/sksl/runtime/LargeProgram_ZeroIterFor.stage
@@ -0,0 +1,24 @@
+half4 main(float2 xy)
+{
+	int i = 0;
+	for (int a = 0;a < 10; ++a) 
+	{
+		for (int b = 0;b < 0; ++b) 
+		{
+			for (int c = 0;c < 100; ++c) 
+			{
+				for (int d = 0;d < 100; ++d) 
+				{
+					for (int e = 0;e < 100; ++e) 
+					{
+						for (int f = 0;f < 100; ++f) 
+						{
+							++i;
+						}
+					}
+				}
+			}
+		}
+	}
+	return half4(half4(half(i)));
+}
diff --git a/tests/sksl/runtime_errors/ProgramTooLarge_FlatLoop.skvm b/tests/sksl/runtime_errors/ProgramTooLarge_FlatLoop.skvm
new file mode 100644
index 0000000..eb45f44
--- /dev/null
+++ b/tests/sksl/runtime_errors/ProgramTooLarge_FlatLoop.skvm
@@ -0,0 +1,4 @@
+### Compilation failed:
+
+error: program is too large
+1 error
diff --git a/tests/sksl/runtime_errors/ProgramTooLarge_Functions.skvm b/tests/sksl/runtime_errors/ProgramTooLarge_Functions.skvm
new file mode 100644
index 0000000..eb45f44
--- /dev/null
+++ b/tests/sksl/runtime_errors/ProgramTooLarge_Functions.skvm
@@ -0,0 +1,4 @@
+### Compilation failed:
+
+error: program is too large
+1 error
diff --git a/tests/sksl/runtime_errors/ProgramTooLarge_NestedLoops.skvm b/tests/sksl/runtime_errors/ProgramTooLarge_NestedLoops.skvm
new file mode 100644
index 0000000..eb45f44
--- /dev/null
+++ b/tests/sksl/runtime_errors/ProgramTooLarge_NestedLoops.skvm
@@ -0,0 +1,4 @@
+### Compilation failed:
+
+error: program is too large
+1 error
diff --git a/tests/sksl/runtime_errors/ProgramTooLarge_SplitLoops.skvm b/tests/sksl/runtime_errors/ProgramTooLarge_SplitLoops.skvm
new file mode 100644
index 0000000..eb45f44
--- /dev/null
+++ b/tests/sksl/runtime_errors/ProgramTooLarge_SplitLoops.skvm
@@ -0,0 +1,4 @@
+### Compilation failed:
+
+error: program is too large
+1 error