Add support for builtin blocks

Problem of supporting builtin blocks boils down to how you refer to them
from the API side -- if the reference is ONLY a SPIRV id, then block
members are difficult to work with. Instead, represent a builtin
variable binding as (id, first component, num components). For a single
variable, first component == 0 always.

Bug: b/120799499
Change-Id: If9e99f4c10f73a008e2f5071a95785920c7fbed1
Reviewed-on: https://swiftshader-review.googlesource.com/c/23488
Tested-by: Chris Forbes <chrisforbes@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index fffafdf..060a6ff 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -28,10 +28,7 @@
 			  serialID{serialCounter++}, modes{}
 	{
 		// Simplifying assumptions (to be satisfied by earlier transformations)
-		// - There is exactly one extrypoint in the module, and it's the one we want
-		// - Builtin interface blocks have been split. [Splitting user-defined interface blocks
-		//   without changing layout is impossible in the general case because splitting an array
-		//   of structs produces a weirdly-strided array, which SPIRV can't represent.
+		// - There is exactly one entrypoint in the module, and it's the one we want
 		// - The only input/output OpVariables present are those used by the entrypoint
 
 		for (auto insn : *this)
@@ -116,6 +113,29 @@
 				object.kind = Object::Kind::Type;
 				object.definition = insn;
 				object.sizeInComponents = ComputeTypeSize(insn);
+
+				// A structure is a builtin block if it has a builtin
+				// member. All members of such a structure are builtins.
+				if (insn.opcode() == spv::OpTypeStruct)
+				{
+					auto d = memberDecorations.find(resultId);
+					if (d != memberDecorations.end())
+					{
+						for (auto &m : d->second)
+						{
+							if (m.HasBuiltIn)
+							{
+								object.isBuiltInBlock = true;
+								break;
+							}
+						}
+					}
+				}
+				else if (insn.opcode() == spv::OpTypePointer)
+				{
+					auto pointeeType = insn.word(3);
+					object.isBuiltInBlock = defs[pointeeType].isBuiltInBlock;
+				}
 				break;
 			}
 
@@ -131,31 +151,17 @@
 				object.kind = Object::Kind::Variable;
 				object.definition = insn;
 				object.storageClass = storageClass;
-				object.sizeInComponents = defs[typeId].sizeInComponents;
+
+				auto &type = defs[typeId];
+
+				object.sizeInComponents = type.sizeInComponents;
+				object.isBuiltInBlock = type.isBuiltInBlock;
 
 				// Register builtins
 
-				// TODO: detect the builtin block!
-				auto &d = decorations[resultId];
-				if (storageClass == spv::StorageClassInput)
+				if (storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput)
 				{
-					if (d.HasBuiltIn)
-					{
-						inputBuiltins[d.BuiltIn] = resultId;
-					} else
-					{
-						PopulateInterface(&inputs, resultId);
-					}
-				}
-				if (storageClass == spv::StorageClassOutput)
-				{
-					if (d.HasBuiltIn)
-					{
-						outputBuiltins[d.BuiltIn] = resultId;
-					} else
-					{
-						PopulateInterface(&outputs, resultId);
-					}
+					ProcessInterfaceVariable(object);
 				}
 				break;
 			}
@@ -175,12 +181,65 @@
 				break;
 			}
 
+			case spv::OpCapability:
+				// Various capabilities will be declared, but none affect our code generation at this point.
+			case spv::OpMemoryModel:
+				// Memory model does not affect our code generation until we decide to do Vulkan Memory Model support.
+			case spv::OpEntryPoint:
+				// Due to preprocessing, the entrypoint provides no value.
+				break;
+
 			default:
 				break;    // This is OK, these passes are intentionally partial
 			}
 		}
 	}
 
+	void SpirvShader::ProcessInterfaceVariable(Object const &object)
+	{
+		assert(object.storageClass == spv::StorageClassInput || object.storageClass == spv::StorageClassOutput);
+
+		auto &builtinInterface = (object.storageClass == spv::StorageClassInput) ? inputBuiltins : outputBuiltins;
+		auto &userDefinedInterface = (object.storageClass == spv::StorageClassInput) ? inputs : outputs;
+
+		auto resultId = object.definition.word(2);
+		if (object.isBuiltInBlock)
+		{
+			// walk the builtin block, registering each of its members separately.
+			auto ptrType = defs[object.definition.word(1)].definition;
+			assert(ptrType.opcode() == spv::OpTypePointer);
+			auto pointeeType = ptrType.word(3);
+			auto m = memberDecorations.find(pointeeType);
+			assert(m != memberDecorations.end());        // otherwise we wouldn't have marked the type chain
+			auto structType = defs[pointeeType].definition;
+			auto offset = 0u;
+			auto word = 2u;
+			for (auto &member : m->second)
+			{
+				auto &memberType = defs[structType.word(word)];
+
+				if (member.HasBuiltIn)
+				{
+					builtinInterface[member.BuiltIn] = {resultId, offset, memberType.sizeInComponents};
+				}
+
+				offset += memberType.sizeInComponents;
+				++word;
+			}
+			return;
+		}
+
+		auto d = decorations.find(resultId);
+		if (d != decorations.end() && d->second.HasBuiltIn)
+		{
+			builtinInterface[d->second.BuiltIn] = {resultId, 0, object.sizeInComponents};
+		}
+		else
+		{
+			PopulateInterface(&userDefinedInterface, resultId);
+		}
+	}
+
 	void SpirvShader::ProcessExecutionMode(InsnIterator insn)
 	{
 		auto mode = static_cast<spv::ExecutionMode>(insn.word(2));
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 3e1e31b..d900da0 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -103,6 +103,7 @@
 			InsnIterator definition;
 			spv::StorageClass storageClass;
 			uint32_t sizeInComponents = 0;
+			bool isBuiltInBlock = false;
 
 			enum class Kind
 			{
@@ -199,6 +200,13 @@
 			}
 		};
 
+		struct BuiltinMapping
+		{
+			uint32_t Id;
+			uint32_t FirstComponent;
+			uint32_t SizeInComponents;
+		};
+
 		std::vector<InterfaceComponent> inputs;
 		std::vector<InterfaceComponent> outputs;
 
@@ -207,8 +215,8 @@
 		static volatile int serialCounter;
 		Modes modes;
 		std::unordered_map<uint32_t, Object> defs;
-		std::unordered_map<spv::BuiltIn, uint32_t> inputBuiltins;
-		std::unordered_map<spv::BuiltIn, uint32_t> outputBuiltins;
+		std::unordered_map<spv::BuiltIn, BuiltinMapping> inputBuiltins;
+		std::unordered_map<spv::BuiltIn, BuiltinMapping> outputBuiltins;
 
 		void ProcessExecutionMode(InsnIterator it);
 
@@ -221,6 +229,8 @@
 		void PopulateInterface(std::vector<InterfaceComponent> *iface, uint32_t id);
 
 		uint32_t GetConstantInt(uint32_t id);
+
+		void ProcessInterfaceVariable(Object const &object);
 	};
 }