GLES: Add unit tests for compile time limits

Compiles and links shaders that perform deep nesting of control blocks and calls.

Bug: b/123587120
Change-Id: I8f19611cbe6d36990ad368a600d2d5be20b1ea07
Reviewed-on: https://swiftshader-review.googlesource.com/c/24728
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/tests/GLESUnitTests/unittests.cpp b/tests/GLESUnitTests/unittests.cpp
index 4829b5d..55813b3 100644
--- a/tests/GLESUnitTests/unittests.cpp
+++ b/tests/GLESUnitTests/unittests.cpp
@@ -235,6 +235,8 @@
 
 	ProgramHandles createProgram(const std::string& vs, const std::string& fs)
 	{
+		GLchar buf[1024];
+
 		ProgramHandles ph;
 		ph.program = glCreateProgram();
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
@@ -246,7 +248,8 @@
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 		GLint vsCompileStatus = 0;
 		glGetShaderiv(ph.vertexShader, GL_COMPILE_STATUS, &vsCompileStatus);
-		EXPECT_EQ(vsCompileStatus, GL_TRUE);
+		glGetShaderInfoLog(ph.vertexShader, sizeof(buf), nullptr, buf);
+		EXPECT_EQ(vsCompileStatus, GL_TRUE) << "Compile status: " << std::endl << buf;
 
 		ph.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
 		const char* fsSource[1] = { fs.c_str() };
@@ -255,7 +258,8 @@
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 		GLint fsCompileStatus = 0;
 		glGetShaderiv(ph.fragmentShader, GL_COMPILE_STATUS, &fsCompileStatus);
-		EXPECT_EQ(fsCompileStatus, GL_TRUE);
+		glGetShaderInfoLog(ph.fragmentShader, sizeof(buf), nullptr, buf);
+		EXPECT_EQ(fsCompileStatus, GL_TRUE) << "Compile status: " << std::endl << buf;
 
 		glAttachShader(ph.program, ph.vertexShader);
 		glAttachShader(ph.program, ph.fragmentShader);
@@ -264,7 +268,8 @@
 
 		GLint linkStatus = 0;
 		glGetProgramiv(ph.program, GL_LINK_STATUS, &linkStatus);
-		EXPECT_NE(linkStatus, 0);
+		glGetProgramInfoLog(ph.program, sizeof(buf), nullptr, buf);
+		EXPECT_NE(linkStatus, 0) << "Link status: " << std::endl << buf;
 
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
@@ -316,6 +321,66 @@
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 	}
 
+	std::string replace(std::string str, const std::string& substr, const std::string& replacement)
+	{
+		size_t pos = 0;
+		while((pos = str.find(substr, pos)) != std::string::npos) {
+			str.replace(pos, substr.length(), replacement);
+			pos += replacement.length();
+		}
+		return str;
+	}
+
+	void checkCompiles(std::string v, std::string f)
+	{
+		Initialize(3, false);
+
+		std::string vs =
+			"#version 300 es\n"
+			"in vec4 position;\n"
+			"out float unfoldable;\n"
+			"$INSERT\n"
+			"void main()\n"
+			"{\n"
+			"    unfoldable = position.x;\n"
+			"    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
+			"    gl_Position.x += F(unfoldable);\n"
+			"}\n";
+
+		std::string fs =
+			"#version 300 es\n"
+			"precision mediump float;\n"
+			"in float unfoldable;\n"
+			"out vec4 fragColor;\n"
+			"$INSERT\n"
+			"void main()\n"
+			"{\n"
+			"    fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
+			"    fragColor.x += F(unfoldable);\n"
+			"}\n";
+
+		vs = replace(vs, "$INSERT", (v.length() > 0) ? v : "float F(float ignored) { return 0.0; }");
+		fs = replace(fs, "$INSERT", (f.length() > 0) ? f : "float F(float ignored) { return 0.0; }");
+
+		const ProgramHandles ph = createProgram(vs, fs);
+
+		glUseProgram(ph.program);
+
+		drawQuad(ph.program);
+
+		deleteProgram(ph);
+
+		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
+
+		Uninitialize();
+	}
+
+	void checkCompiles(std::string s)
+	{
+		checkCompiles(s, "");
+		checkCompiles("", s);
+	}
+
 	EGLDisplay getDisplay() const { return display; }
 	EGLConfig getConfig() const { return config; }
 	EGLSurface getSurface() const { return surface; }
@@ -954,41 +1019,41 @@
 {
 	Initialize(3, false);
 
-	const char * data0[] =

-	{

-		"#version 300 es\n"

-		"in mediump vec2 vary;"

-		"out mediump vec4 color;"

-		"void main()"

-		"{\t"

-			"color = vec4(vary, 0.0, 1.0);"

-		"}"

-	};

-	const char * data1[] =

-	{

-		"#version 300 es\n"

-		"layout(location=0) in mediump vec2 pos;"

-		"out mediump vec2 vary;"

-		"void main()"

-		"{\t"

-			"vary = pos;\t"

-			"gl_Position = vec4(pos, 0.0, 1.0);"

-		"}"

-	};

-

-	GLuint vert = glCreateShader(GL_VERTEX_SHADER);

-	GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);

-	GLuint program = glCreateProgram();

-

-	glShaderSource(frag, 1, data0, (const GLint *)0);

-	glAttachShader(program, vert);

-	glCompileShader(frag);

-	glAttachShader(program, frag);

-	glShaderSource(vert, 1, data1, (const GLint *)0);

-	glCompileShader(vert);

-	glLinkProgram(program);

-	glUseProgram(program);

-	glBeginTransformFeedback(GL_POINTS);

+	const char * data0[] =
+	{
+		"#version 300 es\n"
+		"in mediump vec2 vary;"
+		"out mediump vec4 color;"
+		"void main()"
+		"{\t"
+			"color = vec4(vary, 0.0, 1.0);"
+		"}"
+	};
+	const char * data1[] =
+	{
+		"#version 300 es\n"
+		"layout(location=0) in mediump vec2 pos;"
+		"out mediump vec2 vary;"
+		"void main()"
+		"{\t"
+			"vary = pos;\t"
+			"gl_Position = vec4(pos, 0.0, 1.0);"
+		"}"
+	};
+
+	GLuint vert = glCreateShader(GL_VERTEX_SHADER);
+	GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
+	GLuint program = glCreateProgram();
+
+	glShaderSource(frag, 1, data0, (const GLint *)0);
+	glAttachShader(program, vert);
+	glCompileShader(frag);
+	glAttachShader(program, frag);
+	glShaderSource(vert, 1, data1, (const GLint *)0);
+	glCompileShader(vert);
+	glLinkProgram(program);
+	glUseProgram(program);
+	glBeginTransformFeedback(GL_POINTS);
 	glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
 
 	Uninitialize();
@@ -1426,6 +1491,93 @@
 	Uninitialize();
 }
 
+TEST_F(SwiftShaderTest, CompilerLimits_DeepNestedIfs)
+{
+	std::string body = "return 1.0;";
+	for (int i = 0; i < 16; i++)
+	{
+		body = "  if (f > " + std::to_string(i * 0.1f) + ") {\n" + body + "}\n";
+	}
+
+	checkCompiles(
+		"float F(float f) {\n" + body + "  return 0.0f;\n}\n"
+	);
+}
+
+TEST_F(SwiftShaderTest, CompilerLimits_DeepNestedSwitches)
+{
+	std::string body = "return 1.0;";
+	for (int i = 0; i < 16; i++)
+	{
+		body = "  switch (int(f)) {\n case 1:\n  f *= 2.0;\n" + body + "}\n";
+	}
+
+	checkCompiles("float F(float f) {\n" + body + "  return 0.0f;\n}\n");
+}
+
+TEST_F(SwiftShaderTest, CompilerLimits_DeepNestedLoops)
+{
+	std::string loops = "f = f + f * 2.0;";
+	for (int i = 0; i < 16; i++)
+	{
+		auto it = "l" + std::to_string(i);
+		loops = "  for (int " + it + " = 0; " + it + " < i; " + it + "++) {\n" + loops + "}\n";
+	}
+
+	checkCompiles(
+		"float F(float f) {\n"
+		"  int i = (f > 0.0) ? 1 : 0;\n" + loops +
+		"  return f;\n"
+		"}\n"
+	);
+}
+
+TEST_F(SwiftShaderTest, CompilerLimits_DeepNestedCalls)
+{
+	std::string funcs = "float E(float f) { return f * 2.0f; }\n";
+	std::string last = "E";
+	for (int i = 0; i < 16; i++)
+	{
+		std::string f = "C" + std::to_string(i);
+		funcs += "float " + f + "(float f) { return " + last + "(f) + 1.0f; }\n";
+		last = f;
+	}
+
+	checkCompiles(funcs +
+		"float F(float f) { return " + last + "(f); }\n"
+	);
+}
+
+TEST_F(SwiftShaderTest, CompilerLimits_ManyCallSites)
+{
+	std::string calls;
+	for (int i = 0; i < 256; i++)
+	{
+		calls += "  f += C(f);\n";
+	}
+
+	checkCompiles(
+		"float C(float f) { return f * 2.0f; }\n"
+		"float F(float f) {\n" + calls + "  return f;\n}\n"
+	);
+}
+
+TEST_F(SwiftShaderTest, CompilerLimits_DeepNestedCallsInUnusedFunction)
+{
+	std::string funcs = "float E(float f) { return f * 2.0f; }\n";
+	std::string last = "E";
+	for (int i = 0; i < 16; i++)
+	{
+		std::string f = "C" + std::to_string(i);
+		funcs += "float " + f + "(float f) { return " + last + "(f) + 1.0f; }\n";
+		last = f;
+	}
+
+	checkCompiles(funcs +
+		"float F(float f) { return f; }\n"
+	);
+}
+
 #ifndef EGL_ANGLE_iosurface_client_buffer
 #define EGL_ANGLE_iosurface_client_buffer 1
 #define EGL_IOSURFACE_ANGLE 0x3454
@@ -2165,3 +2317,4 @@
 
 	Uninitialize();
 }
+