SpirvShader: Add WriteCFGGraphVizDotFile debug function

Spits out a graphviz .dot file describing the control flow of the shader.

This is very similar to the output of the spirv-cfg tool in SPIRV-Tools. This is just much easier to use, and allows us to add more bespoke information to the graph in the future.

Bug: b/140287657
Change-Id: Id90b065a3599b941b192a5e8105dffac9ba8f566
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/32952
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 70d97bb..927bb89 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -730,6 +730,14 @@
 		it.second.AssignBlockFields();
 	}
 
+#ifdef SPIRV_SHADER_CFG_GRAPHVIZ_DOT_FILEPATH
+	{
+		char path[1024];
+		snprintf(path, sizeof(path), SPIRV_SHADER_CFG_GRAPHVIZ_DOT_FILEPATH, codeSerialID);
+		WriteCFGGraphVizDotFile(path);
+	}
+#endif
+
 	dbgCreateFile();
 }
 
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index b395ca2..dd05d8e 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -1214,6 +1214,10 @@
 	// Helper for calling rr::Yield with res cast to an rr::Int.
 	void Yield(YieldResult res) const;
 
+	// WriteCFGGraphVizDotFile() writes a graphviz dot file of the shader's
+	// control flow to the given file path.
+	void WriteCFGGraphVizDotFile(const char *path) const;
+
 	// OpcodeName() returns the name of the opcode op.
 	// If NDEBUG is defined, then OpcodeName() will only return the numerical code.
 	static std::string OpcodeName(spv::Op op);
diff --git a/src/Pipeline/SpirvShaderControlFlow.cpp b/src/Pipeline/SpirvShaderControlFlow.cpp
index 8261b14..16f329c 100644
--- a/src/Pipeline/SpirvShaderControlFlow.cpp
+++ b/src/Pipeline/SpirvShaderControlFlow.cpp
@@ -23,6 +23,9 @@
 
 #include <queue>
 
+#include <fstream>
+#include <iostream>
+
 namespace sw {
 
 SpirvShader::Block::Block(InsnIterator begin, InsnIterator end)
@@ -729,4 +732,83 @@
 	dbgUpdateActiveLaneMask(mask, state);
 }
 
+void SpirvShader::WriteCFGGraphVizDotFile(const char *path) const
+{
+	std::ofstream file(path);
+	file << "digraph D {" << std::endl;
+	for(auto &func : functions)
+	{
+		file << "  subgraph cluster_function_" << func.first.value() << " {"
+		     << std::endl;
+
+		file << "    label = \"function<" << func.first.value() << ">"
+		     << (func.first == entryPoint ? " (entry point)" : "")
+		     << "\"" << std::endl;
+
+		for(auto &block : func.second.blocks)
+		{
+			file << "    block_" << block.first.value() << " ["
+			     << "shape=circle "
+			     << "label=\"" << block.first.value() << "\""
+			     << "]" << std::endl;
+		}
+		file << std::endl;
+		for(auto &block : func.second.blocks)
+		{
+			file << "    block_" << block.first.value() << " -> {";
+			bool first = true;
+			for(auto outs : block.second.outs)
+			{
+				if(!first) { file << ", "; }
+				file << "block_" << outs.value();
+				first = false;
+			}
+			file << "}" << std::endl;
+		}
+		file << std::endl;
+		for(auto &block : func.second.blocks)
+		{
+			if(block.second.kind == Block::Loop)
+			{
+				if(block.second.mergeBlock != 0)
+				{
+					file << "    block_" << block.first.value() << " -> "
+					     << "block_" << block.second.mergeBlock.value()
+					     << "[label=\"M\" style=dashed color=blue]"
+					     << std::endl;
+				}
+				if(block.second.continueTarget != 0)
+				{
+					file << "    block_" << block.first.value() << " -> "
+					     << "block_" << block.second.continueTarget.value()
+					     << "[label=\"C\" style=dashed color=green]"
+					     << std::endl;
+				}
+			}
+		}
+
+		file << "  }" << std::endl;
+	}
+
+	for(auto &func : functions)
+	{
+		for(auto &block : func.second.blocks)
+		{
+			for(auto insn : block.second)
+			{
+				if(insn.opcode() == spv::OpFunctionCall)
+				{
+					auto target = getFunction(insn.word(3)).entry;
+					file << "    block_" << block.first.value() << " -> "
+					     << "block_" << target.value()
+					     << "[color=\"#00008050\"]"
+					     << std::endl;
+				}
+			}
+		}
+	}
+
+	file << "}" << std::endl;
+}
+
 }  // namespace sw
\ No newline at end of file
diff --git a/src/Pipeline/SpirvShaderDebug.hpp b/src/Pipeline/SpirvShaderDebug.hpp
index 1cf3ce6..959c542 100644
--- a/src/Pipeline/SpirvShaderDebug.hpp
+++ b/src/Pipeline/SpirvShaderDebug.hpp
@@ -20,6 +20,13 @@
 // reduced to 1 and execution is deterministic.
 #define SPIRV_SHADER_ENABLE_DBG 0
 
+// Enable this to write a GraphViz dot file containing a graph of the shader's
+// control flow to the given file path. Helpful for diagnosing control-flow
+// related issues.
+#if 0
+#	define SPIRV_SHADER_CFG_GRAPHVIZ_DOT_FILEPATH "swiftshader_%d.dot"
+#endif
+
 #if SPIRV_SHADER_ENABLE_DBG
 #	define SPIRV_SHADER_DBG(fmt, ...) rr::Print(fmt "\n", ##__VA_ARGS__)
 #	include "spirv-tools/libspirv.h"