LocalSizeId for VK_KHR_maintenance4

This CL adds support in SPIR-V for spv::ExecutionModeLocalSizeId and
spv::OpExecutionModeId. This is required for VK_KHR_maintenance4
support.

Bug: b/204502926
Change-Id: I00113b9f0e649c49c2c81b68aedd8340ea003297
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/63294
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
Commit-Queue: Alexis Hétu <sugoi@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Pipeline/ComputeProgram.cpp b/src/Pipeline/ComputeProgram.cpp
index 3a771f0..f235d8fb 100644
--- a/src/Pipeline/ComputeProgram.cpp
+++ b/src/Pipeline/ComputeProgram.cpp
@@ -217,10 +217,12 @@
     uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ,
     uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ)
 {
-	auto &executionModes = shader->getExecutionModes();
+	uint32_t workgroupSizeX = shader->getWorkgroupSizeX();
+	uint32_t workgroupSizeY = shader->getWorkgroupSizeY();
+	uint32_t workgroupSizeZ = shader->getWorkgroupSizeZ();
 
 	auto invocationsPerSubgroup = SIMD::Width;
-	auto invocationsPerWorkgroup = executionModes.WorkgroupSizeX * executionModes.WorkgroupSizeY * executionModes.WorkgroupSizeZ;
+	auto invocationsPerWorkgroup = workgroupSizeX * workgroupSizeY * workgroupSizeZ;
 	auto subgroupsPerWorkgroup = (invocationsPerWorkgroup + invocationsPerSubgroup - 1) / invocationsPerSubgroup;
 
 	Data data;
@@ -230,9 +232,9 @@
 	data.numWorkgroups[Y] = groupCountY;
 	data.numWorkgroups[Z] = groupCountZ;
 	data.numWorkgroups[3] = 0;
-	data.workgroupSize[X] = executionModes.WorkgroupSizeX;
-	data.workgroupSize[Y] = executionModes.WorkgroupSizeY;
-	data.workgroupSize[Z] = executionModes.WorkgroupSizeZ;
+	data.workgroupSize[X] = workgroupSizeX;
+	data.workgroupSize[Y] = workgroupSizeY;
+	data.workgroupSize[Z] = workgroupSizeZ;
 	data.workgroupSize[3] = 0;
 	data.invocationsPerSubgroup = invocationsPerSubgroup;
 	data.invocationsPerWorkgroup = invocationsPerWorkgroup;
@@ -273,7 +275,7 @@
 					// Make a function call per subgroup so each subgroup
 					// can yield, bringing all subgroups to the barrier
 					// together.
-					for(int subgroupIndex = 0; subgroupIndex < subgroupsPerWorkgroup; subgroupIndex++)
+					for(uint32_t subgroupIndex = 0; subgroupIndex < subgroupsPerWorkgroup; subgroupIndex++)
 					{
 						auto coroutine = (*this)(device, &data, groupX, groupY, groupZ, workgroupMemory.data(), subgroupIndex, 1);
 						coroutines.push(std::move(coroutine));
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 446059d..d3fb589 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -99,6 +99,7 @@
 			break;
 
 		case spv::OpExecutionMode:
+		case spv::OpExecutionModeId:
 			ProcessExecutionMode(insn);
 			break;
 
@@ -396,6 +397,7 @@
 					executionModes.WorkgroupSizeX = object.constantValue[0];
 					executionModes.WorkgroupSizeY = object.constantValue[1];
 					executionModes.WorkgroupSizeZ = object.constantValue[2];
+					executionModes.useWorkgroupSizeId = false;
 				}
 			}
 			break;
@@ -1014,9 +1016,11 @@
 		executionModes.DepthUnchanged = true;
 		break;
 	case spv::ExecutionModeLocalSize:
+	case spv::ExecutionModeLocalSizeId:
 		executionModes.WorkgroupSizeX = insn.word(3);
 		executionModes.WorkgroupSizeY = insn.word(4);
 		executionModes.WorkgroupSizeZ = insn.word(5);
+		executionModes.useWorkgroupSizeId = (mode == spv::ExecutionModeLocalSizeId);
 		break;
 	case spv::ExecutionModeOriginUpperLeft:
 		// This is always the case for a Vulkan shader. Do nothing.
@@ -1026,6 +1030,21 @@
 	}
 }
 
+uint32_t SpirvShader::getWorkgroupSizeX() const
+{
+	return executionModes.useWorkgroupSizeId ? getObject(executionModes.WorkgroupSizeX).constantValue[0] : executionModes.WorkgroupSizeX.value();
+}
+
+uint32_t SpirvShader::getWorkgroupSizeY() const
+{
+	return executionModes.useWorkgroupSizeId ? getObject(executionModes.WorkgroupSizeY).constantValue[0] : executionModes.WorkgroupSizeY.value();
+}
+
+uint32_t SpirvShader::getWorkgroupSizeZ() const
+{
+	return executionModes.useWorkgroupSizeId ? getObject(executionModes.WorkgroupSizeZ).constantValue[0] : executionModes.WorkgroupSizeZ.value();
+}
+
 uint32_t SpirvShader::ComputeTypeSize(InsnIterator insn)
 {
 	// Types are always built from the bottom up (with the exception of forward ptrs, which
@@ -1806,6 +1825,7 @@
 	case spv::OpTypeSampledImage:
 	case spv::OpTypeSampler:
 	case spv::OpExecutionMode:
+	case spv::OpExecutionModeId:
 	case spv::OpMemoryModel:
 	case spv::OpFunction:
 	case spv::OpFunctionEnd:
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 6c90535..6aab7a7 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -631,9 +631,10 @@
 		bool DepthUnchanged : 1;
 
 		// Compute workgroup dimensions
-		int WorkgroupSizeX = 1;
-		int WorkgroupSizeY = 1;
-		int WorkgroupSizeZ = 1;
+		Object::ID WorkgroupSizeX = 1;
+		Object::ID WorkgroupSizeY = 1;
+		Object::ID WorkgroupSizeZ = 1;
+		bool useWorkgroupSizeId = false;
 	};
 
 	const ExecutionModes &getExecutionModes() const
@@ -879,6 +880,10 @@
 	void emitEpilog(SpirvRoutine *routine) const;
 	void clearPhis(SpirvRoutine *routine) const;
 
+	uint32_t getWorkgroupSizeX() const;
+	uint32_t getWorkgroupSizeY() const;
+	uint32_t getWorkgroupSizeZ() const;
+
 	bool containsImageWrite() const { return imageWriteEmitted; }
 
 	using BuiltInHash = std::hash<std::underlying_type<spv::BuiltIn>::type>;
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 782aecc..79ed55d 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -93,6 +93,7 @@
 	spvtools::ValidatorOptions validatorOptions = {};
 	validatorOptions.SetScalarBlockLayout(true);            // VK_EXT_scalar_block_layout
 	validatorOptions.SetUniformBufferStandardLayout(true);  // VK_KHR_uniform_buffer_standard_layout
+	validatorOptions.SetAllowLocalSizeId(true);             // VK_KHR_maintenance4
 	optimizerOptions.set_validator_options(validatorOptions);
 #endif
 
diff --git a/src/Vulkan/VkShaderModule.cpp b/src/Vulkan/VkShaderModule.cpp
index c97be88..ea75eae 100644
--- a/src/Vulkan/VkShaderModule.cpp
+++ b/src/Vulkan/VkShaderModule.cpp
@@ -25,7 +25,7 @@
 {
 #if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
 	spvtools::SpirvTools spirvTools(SPIRV_VERSION);
-  spirvTools.SetMessageConsumer([](spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) {
+	spirvTools.SetMessageConsumer([](spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) {
 		switch(level)
 		{
 		case SPV_MSG_FATAL: sw::warn("SPIR-V FATAL: %d:%d %s\n", int(position.line), int(position.column), message);
@@ -41,6 +41,7 @@
 	spvtools::ValidatorOptions validatorOptions = {};
 	validatorOptions.SetScalarBlockLayout(true);            // VK_EXT_scalar_block_layout
 	validatorOptions.SetUniformBufferStandardLayout(true);  // VK_KHR_uniform_buffer_standard_layout
+	validatorOptions.SetAllowLocalSizeId(true);             // VK_KHR_maintenance4
 
 	ASSERT(spirvTools.Validate(binary.data(), binary.size(), validatorOptions));  // The SPIR-V code passed to vkCreateShaderModule must be valid (b/158228522)
 #endif